Merge lp:~zedtux/bzr-gtk/gravatar-urllib2 into lp:bzr-gtk/gtk2
- gravatar-urllib2
- Merge into gtk2
Status: | Merged |
---|---|
Merged at revision: | 723 |
Proposed branch: | lp:~zedtux/bzr-gtk/gravatar-urllib2 |
Merge into: | lp:bzr-gtk/gtk2 |
Diff against target: |
428 lines (+375/-2) 3 files modified
avatarproviders.py (+110/-0) avatarsbox.py (+249/-0) revisionview.py (+16/-2) |
To merge this branch: | bzr merge lp:~zedtux/bzr-gtk/gravatar-urllib2 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jelmer Vernooij | Pending | ||
Review via email: mp+53098@code.launchpad.net |
Commit message
Description of the change
Proposing zedtux's gravatar branch.
Jelmer Vernooij (jelmer) wrote : | # |
Guillaume Hain (zedtux) wrote : | # |
Hello Jelmer,
Thank you for your merge proposal.
> Please avoid boilerplate code like this. Just let external users
> set .image, .email, etc directory. They can always be changed to be
> properties later, if necessary.
Okay, agree. I'm not a python expert, and you are learning me things about it. I did it in that way, because I was thinking that was the right way. Now you've told me that, it make sense to me to write it without property.
Could you explain me when it make sense to use property please?
> It might make sense to rename this to __eq__, so that you can just use
> "==" to compare avatars.
Same as above. I didn't learned it before. Thanks.
> Please avoid "if ... else" in one line as it doesn't work in Python 2.4,
> which we bzr-gtk still supports.
Again, thank you for your knowledge sharing.
> The urllib3 bit probably is no longer valid :)
Completely true.
> You'd probably want "super(
> that does the same thing as "AvatarProvider
Here is another point you are learning me.
Could you give me more details about this please?
Finally, about files: I will do it as you want. For me, a class per file is OK, but if you prefer the other way, I don't care.
How do you do to organize your files?
When do you decide when create a new file or not?
As I'm not at home for this week-end, I will patch my code during next week. Sorry about that.
I'm really happy to contribute to your project. And I hope we will have more collaboration together in the future! :)
Cheers,
Jelmer Vernooij (jelmer) wrote : | # |
On Sat, 2011-03-12 at 11:26 +0000, zedtux wrote:
> > Please avoid boilerplate code like this. Just let external users >
> set .image, .email, etc directory. They can always be changed to be >
> properties later, if necessary. Okay, agree. I'm not a python expert,
> and you are learning me things about it. I did it in that way, because
> I was thinking that was the right way. Now you've told me that, it make
> sense to me to write it without property.
No problem.
> Could you explain me when it make sense to use property please?
If there are side-effects of setting this member variable or if you're
not storing the property in this class but elsewhere you generally want
a property So you could have a "committer_email" property that was
implemented as:
def get_committer_
return self.committer.
committer_email = property(
Though I'm not sure how useful having a property would be in that
example, as opposed to just having the user look at .committer.email.
The nice thing is that if something is a member variable and you'd like
to change it to a property later, you can do that without having to
change the code that uses it.
> > You'd probably want "super(
> > that does the same thing as "AvatarProvider
> Here is another point you are learning me.
> Could you give me more details about this please?
super(AvatarPro
AvatarProverGra
__init__() on it there's no need to provide self as it's an instance
object.
In the second case you're taking the __init__ method from AvatarProvider
and calling that on the current object.
Both things have the same result, except when you have multiple base
classes in which case you're better off using the second mechanism for
clarity. I'm not exactly sure which base class super() will return in
case of multiple inheritance, I suspect it'll return the first base
class.
> Finally, about files: I will do it as you want. For me, a class per file is OK, but if you prefer the other way, I don't care.
> How do you do to organize your files?
> When do you decide when create a new file or not?
Generally we stick related things in the same file, or in the same
package and try to keep them readable but not too long. Having too many
files usually makes it hard to find what you're looking for, for example
if you're trying to figure out what file to import something from.
Python also has a bit of overhead per file that's loaded, which we're
already suffering from in Bazaar.
I realize that's a bit handwavy, I guess roughly anywhere under 2000
lines would be ok.
> As I'm not at home for this week-end, I will patch my code during next week. Sorry about that.
> I'm really happy to contribute to your project. And I hope we will have more collaboration together in the future! :)
No worries, looking forward to the followup patch :)
Cheers,
Jelmer
- 721. By Guillaume Hain
-
Patched code following Jelmer's recommandations
Guillaume Hain (zedtux) wrote : | # |
Alright, I've patched my code.
The only thing I didn't is the AvatarProvider class move into the other file. When I've start to copy my code to move it, I was looking at the same time to your comments, and I saw that you're requesting me to move this class that should be inherited by all other future providers (If one day someone want to implement more providers) in the file of one provider, but it make no sense to me.
This master class actually only add the common attribute size, but it also could implement more attributes/method in the future if needed.
So, we have two possibles:
- If you don't think this class is necessary, I can delete it.
- Otherwise, it should stay in its file.
Tell me what is for you the best way.
After that point, you should merge my code ;)
Jelmer Vernooij (jelmer) wrote : | # |
> Alright, I've patched my code.
> The only thing I didn't is the AvatarProvider class move into the other file.
> When I've start to copy my code to move it, I was looking at the same time to
> your comments, and I saw that you're requesting me to move this class that
> should be inherited by all other future providers (If one day someone want to
> implement more providers) in the file of one provider, but it make no sense to
> me.
>
> This master class actually only add the common attribute size, but it also
> could implement more attributes/method in the future if needed.
>
> So, we have two possibles:
> - If you don't think this class is necessary, I can delete it.
> - Otherwise, it should stay in its file.
I don't see why it would no longer be necessary if it's in the same file - it defines the interface that providers have to implement, that seems like a perfectly reasonable thing to have, even if it's in the same file.
As part of this, I think it would be useful to define a dummy get_base_url() in AvatarProvider that just raises NotImplementedE
If you look at other parts of the bzr code you'll see this is a common pattern - a base class that provides a bunch of methods that all raise NotImplementedError and are then overridden by the subclasses. This makes it easy to see what to implement when you create a subclass and it gives a sane error message if a subclass forgot to provide a particular method.
Cheers,
Jelmer
- 722. By Guillaume Hain
-
Renamed avatarprovider.py to avatarproviders.py
Moved class AvatarProviderGravatar into the avatarproviders.py file
Added get_base_url method into the AvatarProvider class raising NotImplementedError exception
Guillaume Hain (zedtux) wrote : | # |
> As part of this, I think it would be useful to define a dummy get_base_url() in AvatarProvider that just raises NotImplementedE
Done.
Finally, I've renamed file avatarprovider.py to avatarproviders.py, and then moved the AvatarProviderG
It clear like this for me.
If it's the same for you, you can merge it, it's fine for me.
Otherwise, I'm open to your suggestions! :-)
Jelmer Vernooij (jelmer) wrote : | # |
I think it's fine to merge now, but it still adds 5 files, all very small. Would you mind if I brought it down to 1 or 2 ?
- 723. By Guillaume Hain
-
Moved Avatar and AvatarBox classes into avatarsbox file
- 724. By Guillaume Hain
-
Moved AvatarDownloade
rWorker class into avatarsbox.py file
Guillaume Hain (zedtux) wrote : | # |
Alright, it's done. :)
Jelmer Vernooij (jelmer) wrote : | # |
On Tue, 2011-03-15 at 16:52 +0000, zedtux wrote:
> Alright, it's done. :)
Merged, thanks very much!
Cheers,
Jelmer
Preview Diff
1 | === added file 'avatarproviders.py' |
2 | --- avatarproviders.py 1970-01-01 00:00:00 +0000 |
3 | +++ avatarproviders.py 2011-03-15 16:52:28 +0000 |
4 | @@ -0,0 +1,110 @@ |
5 | +# Copyright (C) 2011 by Guillaume Hain (zedtux) <zedtux@zedroot.org> |
6 | +# |
7 | +# This program is free software; you can redistribute it and/or modify |
8 | +# it under the terms of the GNU General Public License as published by |
9 | +# the Free Software Foundation; either version 2 of the License, or |
10 | +# (at your option) any later version. |
11 | +# |
12 | +# This program is distributed in the hope that it will be useful, |
13 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | +# GNU General Public License for more details. |
16 | +# |
17 | +# You should have received a copy of the GNU General Public License |
18 | +# along with this program; if not, write to the Free Software |
19 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
20 | + |
21 | +import Queue |
22 | +import urllib |
23 | +import urllib2 |
24 | +import hashlib |
25 | +import threading |
26 | + |
27 | +class AvatarProvider(object): |
28 | + """ |
29 | + Master class for Avatar providers. |
30 | + |
31 | + All AvatarProviderXxxx classes should inherite from this one |
32 | + and override at least get_base_url. |
33 | + """ |
34 | + def __init__(self, size=80): |
35 | + """ Constructor """ |
36 | + self.size = size |
37 | + |
38 | + def get_base_url(self): |
39 | + """ |
40 | + Override this methode in your provider class in order to return |
41 | + base url of your provider. |
42 | + """ |
43 | + raise NotImplementedError("You must implement the get_base_url method.") |
44 | + |
45 | + |
46 | +class AvatarDownloaderWorker(threading.Thread): |
47 | + """ |
48 | + Threaded worker to retrieve avatar from a provider. |
49 | + |
50 | + It create a persitante connection to the provider in order |
51 | + to get avatars quickly through the same socket (urllib3). |
52 | + """ |
53 | + |
54 | + def __init__(self, provider_method): |
55 | + """ |
56 | + Constructor |
57 | + |
58 | + provider_method: Provider method that return fields |
59 | + to send with the request. |
60 | + """ |
61 | + threading.Thread.__init__(self) |
62 | + self.__stop = threading.Event() |
63 | + self.__queue = Queue.Queue() |
64 | + |
65 | + self.__provider_method = provider_method |
66 | + self.__end_thread = False |
67 | + |
68 | + def stop(self): |
69 | + """ Stop this worker """ |
70 | + self.__end_thread = True |
71 | + self.__stop.set() |
72 | + |
73 | + def set_callback_method(self, method): |
74 | + """ Fire the given callback method when treatment is finished """ |
75 | + self.__callback_method = method |
76 | + |
77 | + def queue(self, id_field): |
78 | + """ |
79 | + Put in Queue the id_field to treat in the thread. |
80 | + This id_field is for example with Gravatar the email address. |
81 | + """ |
82 | + self.__queue.put(id_field) |
83 | + |
84 | + def run(self): |
85 | + """ Worker core code. """ |
86 | + while not self.__end_thread: |
87 | + id_field = self.__queue.get() |
88 | + # Call provider method to get fields to pass in the request |
89 | + url = self.__provider_method(id_field) |
90 | + # Execute the request |
91 | + response = urllib2.urlopen(url) |
92 | + # Fire the callback method |
93 | + if not self.__callback_method is None: |
94 | + self.__callback_method(response, id_field) |
95 | + self.__queue.task_done() |
96 | + |
97 | + |
98 | +class AvatarProviderGravatar(AvatarProvider): |
99 | + """ Gravatar provider """ |
100 | + |
101 | + def __init__(self): |
102 | + """ Constructor """ |
103 | + super(AvatarProviderGravatar, self).__init__() |
104 | + |
105 | + def get_base_url(self): |
106 | + return "http://www.gravatar.com/avatar.php?" |
107 | + |
108 | + def gravatar_id_for_email(self, email): |
109 | + """ Return a converted email address to a gravatar_id """ |
110 | + return self.get_base_url() + \ |
111 | + urllib.urlencode({ |
112 | + 'gravatar_id':hashlib.md5(email.lower()).hexdigest(), |
113 | + 'size':str(self.size) |
114 | + }) |
115 | |
116 | === added file 'avatarsbox.py' |
117 | --- avatarsbox.py 1970-01-01 00:00:00 +0000 |
118 | +++ avatarsbox.py 2011-03-15 16:52:28 +0000 |
119 | @@ -0,0 +1,249 @@ |
120 | +# Copyright (C) 2011 by Guillaume Hain (zedtux) <zedtux@zedroot.org> |
121 | +# |
122 | +# This program is free software; you can redistribute it and/or modify |
123 | +# it under the terms of the GNU General Public License as published by |
124 | +# the Free Software Foundation; either version 2 of the License, or |
125 | +# (at your option) any later version. |
126 | +# |
127 | +# This program is distributed in the hope that it will be useful, |
128 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
129 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
130 | +# GNU General Public License for more details. |
131 | +# |
132 | +# You should have received a copy of the GNU General Public License |
133 | +# along with this program; if not, write to the Free Software |
134 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
135 | + |
136 | +import gtk |
137 | + |
138 | +from bzrlib.plugins.gtk import _i18n |
139 | +from bzrlib.config import parse_username |
140 | +from bzrlib.plugins.gtk.avatarproviders import AvatarProviderGravatar, AvatarDownloaderWorker |
141 | + |
142 | + |
143 | +class Avatar(gtk.Box): |
144 | + """ Author or committer avatar """ |
145 | + |
146 | + def __init__(self, apparent_username): |
147 | + """ Constructor """ |
148 | + gtk.Box.__init__(self) |
149 | + |
150 | + self.apparent_username = apparent_username |
151 | + self.username, self.email = parse_username(apparent_username) |
152 | + self.image = None |
153 | + |
154 | + def __eq__(self, other): |
155 | + """ |
156 | + Return True if attributes of the given avatar |
157 | + match to current object attributes otherwise return False |
158 | + """ |
159 | + return self.apparent_username == other.apparent_username and \ |
160 | + self.name == other.name and \ |
161 | + self.email == other.email |
162 | + |
163 | + # ~~~~~ Public methods ~~~~~ |
164 | + def show_spinner(self): |
165 | + """ |
166 | + Replace the current content of the Avatar with a gtk.Spinner |
167 | + if an email address has been parsed. If not, show an gtk.Label with |
168 | + the translatable 'No email' text. |
169 | + """ |
170 | + if not self.email is "": |
171 | + spinner = gtk.Spinner() |
172 | + spinner.start() |
173 | + self.pack_start(spinner, False) |
174 | + spinner.set_tooltip_text(_i18n("Retrieving avatar for %s...") % self.email) |
175 | + spinner.set_size_request(20, 20) |
176 | + spinner.show() |
177 | + else: |
178 | + no_email = gtk.Label(_i18n("No email")) |
179 | + self.pack_start(no_email) |
180 | + self.set_tooltip_text(self.apparent_username) |
181 | + no_email.show() |
182 | + |
183 | + def show_image(self): |
184 | + """ Replace the current content of the Avatar with the gtk.Image """ |
185 | + if not self.email is "" and self.image: |
186 | + self.remove(self.get_children()[0]) |
187 | + self.pack_start(self.image) |
188 | + self.image.set_tooltip_text(self.apparent_username) |
189 | + self.image.show() |
190 | + |
191 | + |
192 | +class AvatarBox(gtk.HBox): |
193 | + """ Improved gtk.HBox """ |
194 | + |
195 | + def __init__(self, homogeneous=False, spacing=0): |
196 | + """ Constructor """ |
197 | + gtk.HBox.__init__(self, homogeneous, spacing) |
198 | + self.__avatars = {} |
199 | + self.avatar = None |
200 | + self.__displaying = None |
201 | + |
202 | + |
203 | + # ~~~~~ Public methods ~~~~~ |
204 | + def reset_view(self): |
205 | + """ Remove current avatars from the gtk box """ |
206 | + for child in self.get_children(): |
207 | + self.remove(child) |
208 | + self.__displaying = None |
209 | + |
210 | + def have_avatar(self, avatar): |
211 | + """ |
212 | + Return True if this box has registered given avatar, |
213 | + otherwise return False |
214 | + """ |
215 | + return avatar.email in self.__avatars |
216 | + |
217 | + def showing(self, avatar): |
218 | + """ |
219 | + Return True if the displaying avatar is the same |
220 | + as the given one otherwise return False |
221 | + """ |
222 | + return self.__displaying and self.__displaying == avatar |
223 | + |
224 | + def append_avatars_with(self, avatar): |
225 | + """ |
226 | + Append avatars collection with the given one if not already registed |
227 | + otherwise render it back. |
228 | + When an avatar is added this method True, otherwise, if the avatar |
229 | + was in the collection, return False. |
230 | + """ |
231 | + if not avatar.email is "" and not avatar.email in self.__avatars: |
232 | + self.__avatars[avatar.email] = avatar |
233 | + self._new_cell_for_avatar(avatar) |
234 | + return True |
235 | + else: |
236 | + self.and_avatar_email(avatar.email).come_back_to_gui() |
237 | + return False |
238 | + |
239 | + def and_avatar_email(self, email): |
240 | + """ |
241 | + Select the avatar from avatars collection |
242 | + in order to apply future actions |
243 | + """ |
244 | + self.avatar = None |
245 | + if not email is "" and email in self.__avatars: |
246 | + self.avatar = self.__avatars[email] |
247 | + else: |
248 | + self.avatar = None |
249 | + return self |
250 | + |
251 | + def update_avatar_image_from_pixbuf_loader(self, loader): |
252 | + """ Replace the gtk.Spinner with the given loader """ |
253 | + if self.avatar: |
254 | + self.avatar.image = gtk.Image() |
255 | + self.avatar.image.set_from_pixbuf(loader.get_pixbuf()) |
256 | + self.avatar.show_image() |
257 | + self.__displaying = self.avatar |
258 | + |
259 | + def come_back_to_gui(self): |
260 | + """ Render back avatar in the GUI """ |
261 | + if self.avatar: |
262 | + self.pack_start(self.avatar) |
263 | + self.__displaying = self.avatar |
264 | + else: |
265 | + self._show_no_email() |
266 | + |
267 | + |
268 | + # ~~~~~ Private methods ~~~~~~ |
269 | + def _new_cell_for_avatar(self, avatar): |
270 | + """ Create a new cell in this box with a gtk.Spinner """ |
271 | + avatar.show_spinner() |
272 | + self.pack_start(avatar) |
273 | + avatar.show() |
274 | + self.__displaying = avatar |
275 | + |
276 | + def _show_no_email(self): |
277 | + """ Show a gtk.Label with test 'No email' """ |
278 | + no_email = gtk.Label(_i18n("No email")) |
279 | + self.pack_start(no_email) |
280 | + no_email.show() |
281 | + |
282 | + |
283 | +class AvatarsBox(gtk.HBox): |
284 | + """ GTK container for authors and committers avatars """ |
285 | + |
286 | + def __init__(self): |
287 | + """ Constructor """ |
288 | + gtk.HBox.__init__(self, False, 10) |
289 | + |
290 | + self.__committer_box = None |
291 | + self.__authors_box = None |
292 | + self._init_gui() |
293 | + |
294 | + # If more later you want to implement more avatar providers, to it like this: |
295 | + # Create a new class named AvatarProvider + provider_name that inherit from |
296 | + # the AvatarProvider class. |
297 | + # Implement a method that return url to use in the request. |
298 | + # For example, with Gravatar, the method return the complete url |
299 | + # with MD5 hash of the email address and put the value in a gravatar_id field. |
300 | + # Then create a new worker (manage them in a python dictionnary). |
301 | + provider = AvatarProviderGravatar() |
302 | + self.__worker = AvatarDownloaderWorker( |
303 | + provider.gravatar_id_for_email |
304 | + ) |
305 | + # This callback method should be fired bt all workers when a request is done. |
306 | + self.__worker.set_callback_method(self._update_avatar_from_response) |
307 | + self.__worker.start() |
308 | + |
309 | + |
310 | + # ~~~~~ Public methods ~~~~~ |
311 | + def add(self, username, role): |
312 | + """ |
313 | + Add the given username in the right role box and add in the worker queue. |
314 | + Here again: If you want to implement more providers, you should add the |
315 | + avatar request in all workers queue. |
316 | + """ |
317 | + avatar = Avatar(username) |
318 | + if role is "author" and not self._role_box_for("committer").showing(avatar) or role is "committer": |
319 | + if self._role_box_for(role).append_avatars_with(avatar): |
320 | + self.__worker.queue(avatar.email) |
321 | + |
322 | + def merge(self, usernames, role): |
323 | + """ Add avatars from a list """ |
324 | + for username in usernames: |
325 | + self.add(username, role) |
326 | + |
327 | + def reset(self): |
328 | + """ |
329 | + Request a reset view for all boxes in order to show only avatars |
330 | + of the selected line in the revision view screen. |
331 | + """ |
332 | + [self._role_box_for(role).reset_view() for role in ["committer", "author"]] |
333 | + |
334 | + |
335 | + # ~~~~~ Private methods ~~~~~ |
336 | + def _init_gui(self): |
337 | + """ Create boxes where avatars will be displayed """ |
338 | + # 2 gtk.HBox: One for the committer and one for authors |
339 | + # Committer |
340 | + self.__committer_box = AvatarBox() |
341 | + self.__committer_box.set_size_request(80, 80) |
342 | + self.pack_end(self.__committer_box, False) |
343 | + self.__committer_box.show() |
344 | + # Authors |
345 | + self.__authors_box = AvatarBox() |
346 | + self.pack_end(self.__authors_box, False) |
347 | + self.__authors_box.set_spacing(10) |
348 | + self.__authors_box.show() |
349 | + |
350 | + def _update_avatar_from_response(self, response, email): |
351 | + """ |
352 | + Callback method fired by avatar worker when finished. |
353 | + |
354 | + response is a urllib2.urlopen() return value. |
355 | + email is used to identify item from self.__avatars. |
356 | + """ |
357 | + if not email is "": |
358 | + # Convert downloaded image from provider to gtk.Image |
359 | + loader = gtk.gdk.PixbufLoader() |
360 | + loader.write(response.read()) |
361 | + loader.close() |
362 | + |
363 | + for role in ["committer", "author"]: |
364 | + self._role_box_for(role).and_avatar_email(email).update_avatar_image_from_pixbuf_loader(loader) |
365 | + |
366 | + def _role_box_for(self, role): |
367 | + """ Return the gtk.HBox for the given role """ |
368 | + return self.__committer_box if role is "committer" else self.__authors_box |
369 | |
370 | === modified file 'revisionview.py' |
371 | --- revisionview.py 2009-12-09 13:27:53 +0000 |
372 | +++ revisionview.py 2011-03-15 16:52:28 +0000 |
373 | @@ -32,6 +32,9 @@ |
374 | |
375 | from bzrlib.plugins.gtk import icon_path |
376 | |
377 | +from bzrlib.plugins.gtk.avatarsbox import AvatarsBox |
378 | +from bzrlib.plugins.gtk.avatar import Avatar |
379 | + |
380 | try: |
381 | from bzrlib.plugins.gtk import seahorse |
382 | except ImportError: |
383 | @@ -402,13 +405,18 @@ |
384 | |
385 | def _set_revision(self, revision): |
386 | if revision is None: return |
387 | - |
388 | + |
389 | + self.avatarsbox.reset() |
390 | + |
391 | self._revision = revision |
392 | if revision.committer is not None: |
393 | self.committer.set_text(revision.committer) |
394 | + self.avatarsbox.add(revision.committer, "committer") |
395 | else: |
396 | self.committer.set_text("") |
397 | + self.avatarsbox.hide() |
398 | author = revision.properties.get('author', '') |
399 | + self.avatarsbox.merge(revision.get_apparent_authors(), "author") |
400 | if author != '': |
401 | self.author.set_text(author) |
402 | self.author.show() |
403 | @@ -579,10 +587,15 @@ |
404 | self.connect_after('notify::revision', self._update_signature) |
405 | |
406 | def _create_headers(self): |
407 | + self.avatarsbox = AvatarsBox() |
408 | + |
409 | self.table = gtk.Table(rows=5, columns=2) |
410 | self.table.set_row_spacings(6) |
411 | self.table.set_col_spacings(6) |
412 | self.table.show() |
413 | + |
414 | + self.avatarsbox.pack_start(self.table) |
415 | + self.avatarsbox.show() |
416 | |
417 | row = 0 |
418 | |
419 | @@ -673,7 +686,8 @@ |
420 | |
421 | self.connect('notify::revision', self._add_tags) |
422 | |
423 | - return self.table |
424 | + self.avatarsbox.show() |
425 | + return self.avatarsbox |
426 | |
427 | def _create_parents(self): |
428 | hbox = gtk.HBox(True, 3) |
Hi Guillaume,
I think this is ready to land, though I have a few more minor remarks.
Please let me know if you're happy for me to just fix them before
landing or if you'd like fix them yourself.
> differences between files attachment (review-diff.txt) get_username, set_username)
> === added file 'avatar.py'
> --- avatar.py 1970-01-01 00:00:00 +0000
> +++ avatar.py 2011-03-11 21:59:14 +0000
> + # ~~~~~ Properties ~~~~~
> + # username
> + def get_username(self):
> + return self.__username
> + def set_username(self, username):
> + self.__username = username
> + username = property(
> +
> + # Name
> + def get_name(self):
> + return self.__name
> + def set_name(self, name):
> + self.__name = name
> + name = property(get_name, set_name)
> +
> + # Email
> + def get_email(self):
> + return self.__email
> + def set_email(self, email):
> + self.__email = email
> + email = property(get_email, set_email)
> +
> + # Image
> + def get_image(self):
> + return self.__image
> + def set_image(self, image):
> + self.__image = image
> + image = property(get_image, set_image)
Please avoid boilerplate code like this. Just let external users
set .image, .email, etc directory. They can always be changed to be
properties later, if necessary.
> + def is_identical(self, avatar):
> + """
> + Return True if attributes of the given avatar
> + match to current object attributes otherwise return False
> + """
> + return self.username == avatar.username and \
> + self.name == avatar.name and \
> + self.email == avatar.email
It might make sense to rename this to __eq__, so that you can just use
"==" to compare avatars.
> === added file 'avatarbox.py'
> --- avatarbox.py 1970-01-01 00:00:00 +0000
> +++ avatarbox.py 2011-03-11 21:59:14 +0000
> + # ~~~~~ Properties ~~~~~ current_ avatar current_ avatar = avatar get_avatar, set_avatar)
> + # Avatar
> + def get_avatar(self):
> + return self.__
> + def set_avatar(self, avatar):
> + self.__
> + avatar = property(
^ See above.
> + # ~~~~~ Public methods ~~~~~
> + def and_avatar_ email(self, email): avatars[ email] if email in self.__avatars else None
> + """
> + Select the avatar from avatars collection
> + in order to apply future actions
> + """
> + self.avatar = None
> + if not email is "":
> + self.avatar = self.__
> + return self
Please avoid "if ... else" in one line as it doesn't work in Python 2.4,
which we bzr-gtk still supports.
> === added file 'avatardownload erworker. py' rworker. py 1970-01-01 00:00:00 +0000 rworker. py 2011-03-11 21:59:14 +0000
> --- avatardownloade
> +++ avatardownloade
> @@ -0,0 +1,72 @@
> +# Copyright (C) 2011 by Guillaume Hain (zedtux) <email address hidden>
> +#
> +# This program is free software; you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published by
> +# the Free Software Foundation; either version 2 of the License, or
> +# (at your option) any later version.
> +#
> +# This...