Merge lp:~bertrand-rousseau/gtg/bugfix-1038662-dont-touch-due-date into lp:~gtg/gtg/old-trunk
- bugfix-1038662-dont-touch-due-date
- Merge into old-trunk
Status: | Merged |
---|---|
Merged at revision: | 1237 |
Proposed branch: | lp:~bertrand-rousseau/gtg/bugfix-1038662-dont-touch-due-date |
Merge into: | lp:~gtg/gtg/old-trunk |
Diff against target: |
307 lines (+191/-51) 3 files modified
CHANGELOG (+2/-0) GTG/core/task.py (+179/-50) GTG/gtk/browser/treeview_factory.py (+10/-1) |
To merge this branch: | bzr merge lp:~bertrand-rousseau/gtg/bugfix-1038662-dont-touch-due-date |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Izidor Matušov | Approve | ||
Bertrand Rousseau (community) | Needs Resubmitting | ||
Lionel Dricot (community) | Needs Information | ||
Review via email: mp+121929@code.launchpad.net |
Commit message
Description of the change
I changed the due date inheritance mechanism so that due date of children is not altered if it's not set.
Since I can't come up with a good UI to display the constraining date in the task editor, I think the best solution is to leave the due date selector widget blank and not display anything about a constraining date.
- 1233. By Bertrand Rousseau
-
Update CHANGELOG
Lionel Dricot (ploum-deactivatedaccount) wrote : | # |
Izidor Matušov (izidor) wrote : | # |
I don't think the case Lionel talked about is solved correctly. Fuzzy dates are and should be fuzzy: you can put that something is due "now", "soon" or "someday" although the parent is due tomorrow. The user shouldn't care about what soon means - is it 7 or 14 days? Otherwise she might want to configure it => added complexity. Fuzzy dates should be transparent and not constrain any parent or child.
- 1234. By Bertrand Rousseau
-
Merge trunk and update set_due_
date/get_ constrained_ date to avoid applying constrains to fuzzy dates. Also fix a bug: constrained were not applied through undefined tasks.
Bertrand Rousseau (bertrand-rousseau) wrote : | # |
Izidor> Constraints are now not applied anymore to tasks with fuzzy due dates. However, it's not clear for how the task list should interpret some constraints:
If have a task set to "Tomorrow", and a child set to "Soon", what should be displayed for the child task in the task list? "Soon" or "Tomorrow"? IMHO, "Tomorrow" is better since it's more precise. (It's currently implemented that way: I consider precise dates to have the priority on fuzzy dates.)
Another situation: if I have a task set to "Now", and a child set to "Soon", what should be shown for the child? "Now"? Currently, I don't consider constraints between fuzzy dates, but it might make sense. If so, then it would also make sense to update related task due dates even when they are fuzzy, but it's in opposition to Lionel's comment.
And finally, if I have a task with "Soon" and a child with an undefined due date, should I display "Soon" as well for the child? Currently, I don't do this, but it might make sense too since that's what's done for precise dates.
Izidor Matušov (izidor) wrote : | # |
It is hard to say what is right, we should define it on our own. I am proposing the following semantics:
Fuzzy dates "now" and "soon" are fully transparent. It means that a task can have now or soon althoug the parent has a precise date like tomorrow. (They are fuzzy!!!) Children would heritate the fuzzy due date. However, when you put precise date into ancestors, it complains regular limitations. (Fuzzy dates are transparent). Look at the following (valid) structure:
a (tomorrow)
-b (soon)
--c (now)
----d (soon)
-----e (today)
I would say that someday is not valid for any task with parent because you already have deadline and it is something more precise than someday. It means you would always rewrite date.
What do you say?
Bertrand Rousseau (bertrand-rousseau) wrote : | # |
I agree, it is quite hard. For the record, I don't clearly understand
the use case for fuzzy dates and if it were for me alone, I would
remove that feature (at least from core), since I don't really think it
is necessary to GTG. But that's another discussion.
I don't really like the idea of sticking too much to the semantics.
Indeed, since fuzzy dates are fuzzy, everyone might like to interpret
things his/her way. So I'd prefer to leave the complete liberty of
interpretation to the user here, even if it could appear incompatible
to have an ancestor due for "now", and a child due for "someday".
So, I suggest to make ALL fuzzy dates transparent (even "someday"), and
to NOT check for consistency among fuzzy dates (total transparency).
And finally, since fuzzy dates are used at the explicit request of a
user to make those tasks stand out among the others, I propose to keep
showing fuzzy dates instead of precise dates even when an ancestor has
a precise due date.
So all in all, it would correspond to your suggestion and example, with
the addition of "someday".
I think that would allow to give a clear-ish definition of fuzzy dates
to the user: "fuzzy dates are there to provide you with some liberty in
assigning due dates to tasks (until you can clearly decide, for
instance). They don't follow and don't impose any constraining rules
themselves, and they let constaints from parents and children go
through them untouched".
If people seems okay with that (Lionel? Any opinion on this?) That's
what I'll implement.
Bertrand
On Mon 22 Oct 2012 10:47:20 PM CEST, Izidor Matušov wrote:
> It is hard to say what is right, we should define it on our own. I am proposing the following semantics:
>
> Fuzzy dates "now" and "soon" are fully transparent. It means that a task can have now or soon althoug the parent has a precise date like tomorrow. (They are fuzzy!!!) Children would heritate the fuzzy due date. However, when you put precise date into ancestors, it complains regular limitations. (Fuzzy dates are transparent). Look at the following (valid) structure:
>
> a (tomorrow)
> -b (soon)
> --c (now)
> ----d (soon)
> -----e (today)
>
> I would say that someday is not valid for any task with parent because you already have deadline and it is something more precise than someday. It means you would always rewrite date.
>
> What do you say?
--
Bertrand Rousseau
<email address hidden>
Izidor Matušov (izidor) wrote : | # |
I agree, let's go with the fully transparent fuzzy dates!
- 1235. By Bertrand Rousseau
-
Update due date update policy.
Updated the due date update policy as discussed here in launchpad [1]:
ALL fuzzy dates transparent (even "someday"), and we DONT check for consistency
among fuzzy dates (total transparency). And finally, since fuzzy dates are used
at the explicit request of a user to make those tasks stand out among the
others, we keep showing fuzzy dates instead of precise dates even when an
ancestor has a precise due date.
Bertrand Rousseau (bertrand-rousseau) wrote : | # |
I just updated the code to reflect what we discussed here. Please test the branch and tell me if it is ok!
- 1236. By Bertrand Rousseau
-
Ooops: left out a small bug, task list was not displaying constraining due date for undefined but constrained child task
- 1237. By Bertrand Rousseau
-
Fix for bug #1036695: Date constraints after drag and drop not applied
Bertrand Rousseau (bertrand-rousseau) wrote : | # |
I also inspected the code for bug #1036695: "Date constraints after drag and drop not applied" and found a fix. To reduce review work I integrate this fix directly in this merge review.
- 1238. By Bertrand Rousseau
-
Merge trunk
- 1239. By Bertrand Rousseau
-
Oops: accidently removed my fix for constraints update after DnD while merging with the trunk
Izidor Matušov (izidor) wrote : | # |
When I drag a child task to make it a stand alone task, I get the following error:
Problem with dragging: 'NoneType' object has no attribute 'get_due_
Izidor Matušov (izidor) wrote : | # |
Otherwise the patch seems good to me and it works good.
- 1240. By Bertrand Rousseau
-
Fix bug in set_parent
Bertrand Rousseau (bertrand-rousseau) wrote : | # |
Fix the bug found by Izidor ('NoneType' object has no attribute 'get_due_
FYI, It seems that this patch has a negative impact on performances (since constraining due dates are computed dynamically for fuzzy/undefined due date). On "normal" dataset, it is about 3% slower (which represents only 30ms on my laptop), but on big datasets (Bryce-ish datasets), it can go up to 10% (=3s on my laptop). I tried to fix this by updating the value only when required and caching it, but I can't succeed with this approach since during boot time it seems that the method "set_parent" is not used to update the task hierarchy. Consequently, I can't catch all task relations updates and the due date constraints are not correctly set up. I have no idea how to catch task relations updates any other way than that, so I don't know how to fix this. Now, since the impact is quite limited, I guess we could still merge the patch.
Izidor Matušov (izidor) : | # |
Preview Diff
1 | === modified file 'CHANGELOG' |
2 | --- CHANGELOG 2012-11-01 10:09:06 +0000 |
3 | +++ CHANGELOG 2012-11-01 11:54:31 +0000 |
4 | @@ -54,6 +54,8 @@ |
5 | * Fix for bug #1036955: Due date is not preselected when start date is filled, by Steve Scheel |
6 | * Fix for bug #1045036: Slovak Translation Updated by Slavko |
7 | * Remove use of liblarch's "transparent" concept (since it's been removed from liblarch), fixes bugs #1001962, #1001962, #1069257, #1069963: intermediary tags, counter initialization, and regressions caused by initial versions of the patch |
8 | + * Fix for bug #1038662: Undefined due dates in subtasks should always stay undefined and displayed as such in the editor |
9 | + * Fix for bug #1036695: Date constraints after drag and drop not applied |
10 | |
11 | 2012-02-13 Getting Things GNOME! 0.2.9 |
12 | * Big refractorization of code, now using liblarch |
13 | |
14 | === modified file 'GTG/core/task.py' |
15 | --- GTG/core/task.py 2012-11-01 10:09:06 +0000 |
16 | +++ GTG/core/task.py 2012-11-01 11:54:31 +0000 |
17 | @@ -246,68 +246,177 @@ |
18 | def set_modified(self, modified): |
19 | self.last_modified = modified |
20 | |
21 | - def set_due_date(self, fulldate): |
22 | + def recursive_sync(self): |
23 | + """Recursively sync the task and all task children. Defined""" |
24 | + self.sync() |
25 | + for sub_id in self.children: |
26 | + sub = self.req.get_task(sub_id) |
27 | + sub.recursive_sync() |
28 | + |
29 | + # ABOUT DUE DATES |
30 | + # |
31 | + # PLEASE READ THIS: although simple in appearance, handling task dates can |
32 | + # actually be subtle. Take the time to understand this if you plan to work |
33 | + # on the methods below. |
34 | + # |
35 | + # Due date is the date at which a task must be accomplished. Constraints |
36 | + # exist between a task's due date and its ancestor/children's due dates. |
37 | + # |
38 | + # Date constraints |
39 | + # |
40 | + # Those are the following: |
41 | + # - children of a task cannot have a task due date that happens later than |
42 | + # the task's due date |
43 | + # - ancestors of a task cannot have a due that happens before the |
44 | + # task's due date (this is the reverse constraint from the first one) |
45 | + # - a task's start date cannot happen later than this task's due date |
46 | + # |
47 | + # Tasks with undefined or fuzzy due dates |
48 | + # |
49 | + # Task with no due date (="undefined" tasks) or tasks with fuzzy start/due |
50 | + # dates are not subject to constraints. Furthermore, they are "transparent". |
51 | + # Meaning that they let the constraints coming from their children/parents |
52 | + # pass through them. So, for instance, a children of a task with an |
53 | + # undefined or fuzzy task would be constrained by this latter task's |
54 | + # ancestors. Equally, the an ancestor from the same undefined/fuzzy task |
55 | + # would be constrained by the children due dates. |
56 | + # |
57 | + # Updating a task due date |
58 | + # |
59 | + # Whenever a task due date is changed, all ancestor/chldren of this task |
60 | + # *must* be updated according to the constraining rules. As said above, |
61 | + # constraints must go through tasks with undefined/fuzzy due dates too! |
62 | + # |
63 | + # Undefined/fuzzy task dates are NEVER to be updated. They are not sensitive |
64 | + # to constraint. If you want to now what constraint there is on this task's |
65 | + # due date though, you can obtain it by using the get_due_date_constraint |
66 | + # method. |
67 | + |
68 | + def set_due_date(self, new_duedate): |
69 | """Defines the task's due date.""" |
70 | - def get_due_date_constraint(): |
71 | - """ Returns the most urgent due date constraint, following |
72 | - parents' due dates. Return Date.no_date() if no constraint |
73 | - is applied. """ |
74 | - cur_date = Date.no_date() |
75 | - for par_id in self.get_parents(): |
76 | - par = self.req.get_task(par_id) |
77 | - if par.get_due_date() != Date.no_date() and \ |
78 | - par.get_due_date() < cur_date: |
79 | - cur_date = par.get_due_date() |
80 | - return cur_date |
81 | - fulldate_obj = Date(fulldate) # caching the conversion |
82 | - self.due_date = fulldate_obj |
83 | - # if the task's start date happens later than the |
84 | - # new due date, we update it |
85 | - # (except for fuzzy dates) |
86 | - if self.get_start_date() != Date.no_date() and \ |
87 | - not fulldate_obj.is_fuzzy() and \ |
88 | - self.get_start_date() > fulldate_obj: |
89 | - self.set_start_date(fulldate) |
90 | - if fulldate_obj != Date.no_date(): |
91 | - # if the parent's due date happens before the task's new |
92 | - # due date, we update it |
93 | - for par_id in self.parents: |
94 | - par = self.req.get_task(par_id) |
95 | - if par.get_due_date() != Date.no_date() and \ |
96 | - par.get_due_date() < fulldate_obj: |
97 | - par.set_due_date(fulldate) |
98 | - # the current task being one of its children's parents, we must |
99 | - # apply the constraints on their due/start dates as well |
100 | - for sub_id in self.children: |
101 | - sub = self.req.get_task(sub_id) |
102 | - # child's due date is not set, we use the task's new |
103 | - # due date |
104 | - if sub.get_due_date() == Date.no_date(): |
105 | - sub.set_due_date(fulldate) |
106 | - # child's due date happens later than the task's: we |
107 | + |
108 | + def __get_defined_parent_list(task): |
109 | + """Recursively fetch a list of parents that have a defined due date |
110 | + which is not fuzzy""" |
111 | + parent_list = [] |
112 | + for par_id in task.parents: |
113 | + par = self.req.get_task(par_id) |
114 | + if par.get_due_date() == Date.no_date() or \ |
115 | + ( par.get_due_date() != Date.no_date() and \ |
116 | + par.get_due_date().is_fuzzy() ): |
117 | + parent_list += __get_defined_parent_list(par) |
118 | + else: |
119 | + parent_list.append(par) |
120 | + return parent_list |
121 | + |
122 | + def __get_defined_child_list(task): |
123 | + """Recursively fetch a list of children that have a defined due date |
124 | + which is not fuzzy""" |
125 | + child_list = [] |
126 | + for child_id in task.children: |
127 | + child = self.req.get_task(child_id) |
128 | + if child.get_due_date() == Date.no_date() or \ |
129 | + ( child.get_due_date() != Date.no_date() and \ |
130 | + child.get_due_date().is_fuzzy() ): |
131 | + child_list += __get_defined_child_list(child) |
132 | + else: |
133 | + child_list.append(child) |
134 | + return child_list |
135 | + |
136 | + old_due_date = self.due_date |
137 | + new_duedate_obj = Date(new_duedate) # caching the conversion |
138 | + self.due_date = new_duedate_obj |
139 | + # If the new date is fuzzy or undefined, we don't update related tasks |
140 | + if not new_duedate_obj.is_fuzzy() and new_duedate_obj != Date.no_date(): |
141 | + # if the task's start date happens later than the |
142 | + # new due date, we update it (except for fuzzy dates) |
143 | + if self.get_start_date() != Date.no_date() and \ |
144 | + not self.get_start_date().is_fuzzy() and \ |
145 | + self.get_start_date() > new_duedate_obj: |
146 | + self.set_start_date(new_duedate) |
147 | + # if some ancestors' due dates happen before the task's new |
148 | + # due date, we update them (except for fuzzy dates) |
149 | + for par in __get_defined_parent_list(self): |
150 | + if par.get_due_date() < new_duedate_obj: |
151 | + par.set_due_date(new_duedate) |
152 | + # we must apply the constraints to the defined & non-fuzzy children |
153 | + # as well |
154 | + for sub in __get_defined_child_list(self): |
155 | + sub_duedate = sub.get_due_date() |
156 | + # if the child's due date happens later than the task's: we |
157 | # update it to the task's new due date |
158 | - # (= the new most restrictive) |
159 | - if sub.get_due_date() != Date.no_date() and \ |
160 | - sub.get_due_date() > fulldate_obj: |
161 | - sub.set_due_date(fulldate) |
162 | + if sub_duedate > new_duedate_obj: |
163 | + sub.set_due_date(new_duedate) |
164 | # if the child's start date happens later than |
165 | # the task's new due date, we update it |
166 | - # (except for fuzzy dates) |
167 | - if sub.get_start_date() != Date.no_date() and \ |
168 | - not fulldate_obj.is_fuzzy() and \ |
169 | - sub.get_start_date() > fulldate_obj: |
170 | - sub.set_start_date(fulldate) |
171 | - else: |
172 | - self.due_date = get_due_date_constraint() |
173 | - self.sync() |
174 | + # (except for fuzzy start dates) |
175 | + sub_startdate = sub.get_start_date() |
176 | + if sub_startdate != Date.no_date() and \ |
177 | + not sub_startdate.is_fuzzy() and \ |
178 | + sub_startdate > new_duedate_obj: |
179 | + sub.set_start_date(new_duedate) |
180 | + # If the date changed, we notify the change for the children since the |
181 | + # constraints might have changed |
182 | + if old_due_date != new_duedate_obj: |
183 | + self.recursive_sync() |
184 | |
185 | def get_due_date(self): |
186 | """ Returns the due date, which always respects all constraints """ |
187 | return self.due_date |
188 | |
189 | + def get_due_date_constraint(self): |
190 | + """ Returns the most urgent due date constraint, following |
191 | + parents' due dates. Return Date.no_date() if no constraint |
192 | + is applied. """ |
193 | + # Check out for constraints depending on date definition/fuzziness. |
194 | + strongest_const_date = self.due_date |
195 | + if strongest_const_date == Date.no_date() or \ |
196 | + ( strongest_const_date != Date.no_date() and \ |
197 | + strongest_const_date.is_fuzzy() ): |
198 | + for par_id in self.parents: |
199 | + par = self.req.get_task(par_id) |
200 | + par_duedate = par.get_due_date() |
201 | + # if parent date is undefined or fuzzy, look further up |
202 | + if par_duedate == Date.no_date() or \ |
203 | + par_duedate.is_fuzzy(): |
204 | + par_duedate = par.get_due_date_constraint() |
205 | + # if par_duedate is still undefined/fuzzy, all parents' due |
206 | + # dates are undefined or fuzzy: strongest_const_date is then |
207 | + # the best choice so far, we don't update it. |
208 | + if par_duedate == Date.no_date() or \ |
209 | + par_duedate.is_fuzzy(): |
210 | + continue |
211 | + # par_duedate is not undefined/fuzzy. If strongest_const_date is |
212 | + # still undefined or fuzzy, parent_duedate is the best choice. |
213 | + if strongest_const_date == Date.no_date() or \ |
214 | + ( strongest_const_date != Date.no_date() and \ |
215 | + strongest_const_date.is_fuzzy() ): |
216 | + strongest_const_date = par_duedate |
217 | + continue |
218 | + # strongest_const_date and par_date are defined and not fuzzy: |
219 | + # we compare the dates |
220 | + if par_duedate < strongest_const_date: |
221 | + strongest_const_date = par_duedate |
222 | + return strongest_const_date |
223 | + |
224 | + # ABOUT START DATE |
225 | + # |
226 | + # Start date is the date at which the user has decided to work or consider |
227 | + # working on this task. |
228 | + # |
229 | + # The only constraint applied to start dates is that start dates cannot |
230 | + # happen later than the task due date. |
231 | + # |
232 | + # The task due date (and any constrained relatives) is updated if a new |
233 | + # task start date is chosen that does not respect this rule. |
234 | + # |
235 | + # Undefined/fizzy start dates don't constraint the task due date. |
236 | + |
237 | def set_start_date(self, fulldate): |
238 | self.start_date = Date(fulldate) |
239 | if Date(fulldate) != Date.no_date() and \ |
240 | + not Date(fulldate).is_fuzzy() and \ |
241 | + self.due_date != Date.no_date() and \ |
242 | not self.due_date.is_fuzzy() and \ |
243 | Date(fulldate) > self.due_date: |
244 | self.set_due_date(fulldate) |
245 | @@ -316,6 +425,12 @@ |
246 | def get_start_date(self): |
247 | return self.start_date |
248 | |
249 | + # ABOUT CLOSED DATE |
250 | + # |
251 | + # Closed date is the date at which the task has been closed (done or |
252 | + # dismissed). Closed date is not constrained and doesn't constrain other |
253 | + # dates. |
254 | + |
255 | def set_closed_date(self, fulldate): |
256 | self.closed_date = Date(fulldate) |
257 | self.sync() |
258 | @@ -476,6 +591,20 @@ |
259 | """ |
260 | return self.req.get_task(tid) |
261 | |
262 | + def set_parent(self, parent_id): |
263 | + """Update the task's parent. Refresh due date constraints.""" |
264 | + TreeNode.set_parent(self, parent_id) |
265 | + if parent_id is not None: |
266 | + par = self.req.get_task(parent_id) |
267 | + par_duedate = par.get_due_date_constraint() |
268 | + if par_duedate != Date.no_date() and \ |
269 | + not par_duedate.is_fuzzy() and \ |
270 | + self.due_date != Date.no_date() and \ |
271 | + not self.due_date.is_fuzzy() and \ |
272 | + par_duedate < self.due_date: |
273 | + self.set_due_date(par_duedate) |
274 | + self.recursive_sync() |
275 | + |
276 | def set_attribute(self, att_name, att_value, namespace=""): |
277 | """Set an arbitrary attribute. |
278 | |
279 | |
280 | === modified file 'GTG/gtk/browser/treeview_factory.py' |
281 | --- GTG/gtk/browser/treeview_factory.py 2012-10-31 15:43:32 +0000 |
282 | +++ GTG/gtk/browser/treeview_factory.py 2012-11-01 11:54:31 +0000 |
283 | @@ -122,7 +122,12 @@ |
284 | return node.get_start_date().to_readable_string() |
285 | |
286 | def task_duedate_column(self,node): |
287 | - return node.get_due_date().to_readable_string() |
288 | + # We show the most constraining due date for task with no due dates. |
289 | + if node.get_due_date() == Date.no_date(): |
290 | + return node.get_due_date_constraint().to_readable_string() |
291 | + else: |
292 | + # Other tasks show their due date (which *can* be fuzzy) |
293 | + return node.get_due_date().to_readable_string() |
294 | |
295 | def task_cdate_column(self,node): |
296 | return node.get_closed_date().to_readable_string() |
297 | @@ -154,6 +159,10 @@ |
298 | elif para == 'due': |
299 | t1 = task1.get_due_date() |
300 | t2 = task2.get_due_date() |
301 | + if t1 == Date.no_date(): |
302 | + t1 = task1.get_due_date_constraint() |
303 | + if t2 == Date.no_date(): |
304 | + t2 = task2.get_due_date_constraint() |
305 | elif para == 'closed': |
306 | t1 = task1.get_closed_date() |
307 | t2 = task2.get_closed_date() |
I've tested and I see a problem :
1. Set the parent due date to "soon"
2. Open the children and set due date to "now".
Everything is fine.
3. Now set child due date to "later": immediately, the parent due date is changed too.
Is that what we really want ?