*** alga has joined #SchoolTool | 00:06 | |
*** replaceafill has quit IRC | 00:20 | |
*** povbot has joined #schooltool | 03:34 | |
*** th1a has quit IRC | 05:39 | |
*** ignas has joined #schooltool | 11:53 | |
*** alga has quit IRC | 12:47 | |
*** mgedmin has joined #schooltool | 13:07 | |
*** alga has joined #SchoolTool | 13:23 | |
*** ignas has quit IRC | 14:24 | |
*** th1a has joined #schooltool | 16:07 | |
*** yvl has quit IRC | 16:22 | |
*** ignas has joined #schooltool | 16:24 | |
ignas | th1a: ping | 16:29 |
---|---|---|
*** replaceafill has joined #schooltool | 16:39 | |
replaceafill | ignas, ping | 16:51 |
ignas | pong | 16:55 |
replaceafill | i'm going to change the implementation to use adapters | 16:55 |
replaceafill | could i create a new branch for it | 16:55 |
replaceafill | using the procedure aelkner told me yesterday | 16:55 |
ignas | thinking | 17:01 |
replaceafill | :) | 17:02 |
replaceafill | i just dont want to put more work on u | 17:02 |
replaceafill | thats all | 17:02 |
ignas | well - i didn't do much work yet anyway | 17:05 |
ignas | just looked at what was done | 17:05 |
replaceafill | ok to understand clearly: | 17:05 |
replaceafill | 1. bzr branch gradebook trunk | 17:06 |
replaceafill | 2. make my changes there | 17:06 |
replaceafill | 3. push to a new branch on my own folder with a suffix like _integration | 17:06 |
replaceafill | right? | 17:06 |
ignas | a sec | 17:06 |
ignas | ok | 17:14 |
ignas | now | 17:14 |
ignas | suffix should be | 17:14 |
ignas | _external_activities | 17:14 |
ignas | also - the things i disliked about the current state of code | 17:14 |
ignas | utilities instead of adapters are not really giving any benefits | 17:14 |
replaceafill | ok | 17:15 |
ignas | except that you can't dispatch according to section type | 17:15 |
ignas | or some special logic | 17:15 |
ignas | titles as connecting attribute | 17:15 |
ignas | do you really want that? | 17:15 |
ignas | i mean - people can change titles | 17:15 |
replaceafill | yes | 17:15 |
ignas | what will happen if someone will add one more external activity source | 17:15 |
ignas | and that other source will have a title from one of the sources that existed before | 17:15 |
ignas | what happens when external activities disappear? | 17:16 |
ignas | i mean - if i will delete a skilldriver in cando | 17:16 |
ignas | what will happen to the linked activity? | 17:16 |
ignas | what will happen if i will rename activity in cando? | 17:17 |
replaceafill | but if storing adapters as attributes is not the solution | 17:17 |
ignas | you are not storing adapters anyway | 17:17 |
replaceafill | how can we link them? | 17:17 |
ignas | i you want to keep persistent information about the connection | 17:17 |
ignas | you need a separate persistent object | 17:17 |
ignas | now - how that persistent object finds out what activities it is linking is another problem | 17:18 |
ignas | at the moment you are using titles for that | 17:18 |
ignas | but you could also find some other way to refer to an external activity provided by the utility/adapter | 17:19 |
ignas | or you can refer to the external activity directly | 17:19 |
ignas | and use subscribers | 17:19 |
ignas | to remove the linked activity if the external gets deleted | 17:19 |
ignas | or you can add code in the adapter/utility | 17:20 |
ignas | that can lookup the external activity in any way it needs | 17:20 |
ignas | by some unique token | 17:20 |
ignas | so IExternalActivities(section) | 17:20 |
ignas | can list external activities | 17:20 |
ignas | look up external activity by some key | 17:20 |
ignas | and give a unique key for any activity it returns | 17:20 |
ignas | not sure how it should be implemented though | 17:21 |
ignas | as I haven't seen the cando part of the code | 17:21 |
ignas | oh and how does: | 17:21 |
ignas | + def getExternalActivity(self): | 17:21 |
ignas | + section = self.__parent__.__parent__.__parent__ | 17:21 |
ignas | + try: | 17:21 |
ignas | + return [activity | 17:21 |
ignas | + for activity in interfaces.IExternalActivities(section) | 17:21 |
ignas | + if self.title == activity.title][0] | 17:21 |
ignas | + except (IndexError,): | 17:21 |
ignas | + return None | 17:21 |
ignas | even work? | 17:21 |
ignas | the if has no ":" after it | 17:21 |
ignas | aggg | 17:21 |
ignas | ahhh | 17:21 |
ignas | sorry | 17:22 |
ignas | my bad | 17:22 |
ignas | misread it | 17:22 |
replaceafill | :) | 17:22 |
ignas | ouch | 17:22 |
ignas | so if you find more than 1 activity | 17:22 |
ignas | with the same title | 17:22 |
ignas | you return the first one | 17:22 |
ignas | emm - so if i add one more activity with the same title | 17:22 |
ignas | your code can suddenly start using the new activity | 17:22 |
ignas | even though the old one was linked | 17:22 |
replaceafill | i completly agree | 17:23 |
ignas | so that's what i am worried about | 17:23 |
replaceafill | the whole code :D | 17:23 |
replaceafill | writing it all down... | 17:26 |
th1a | ignas: pong | 17:27 |
ignas | th1a: how many and which days are we going to spend programming? | 17:31 |
th1a | It seems to me that your plan covers everything I would have wanted to talk about, so we should be able to plow right into coding. | 17:32 |
th1a | As much as is actually possible. | 17:32 |
ignas | yeah | 17:32 |
ignas | i am talking dates | 17:32 |
ignas | for flying | 17:32 |
ignas | ;) | 17:32 |
th1a | Oh, yeah. ;-) | 17:32 |
th1a | 3 days of coding Feb. 6, 7, 8. | 17:34 |
ignas | ok, so i will try to get a flight to get there on 5 and come back on 10 or maybe just maybe 11th | 17:36 |
th1a | OK. Let me know. | 17:37 |
*** fsufitch has quit IRC | 17:48 | |
*** fsufitch has joined #schooltool | 17:52 | |
ignas | th1a: i just tried to find out how much it costs and how long it takes to go from Washington D.C. to New York by train | 17:52 |
ignas | th1a: let's say i am slightly disappointed by the backwardness of Amtrak | 17:53 |
ignas | replaceafill: not the whole code, the idea to have them enabled on demand and not automatically is something i didn't even think of | 17:56 |
ignas | on the other hand the functional test that has john...5...6...29 | 17:57 |
ignas | is something i would not do really | 17:57 |
ignas | i mean - it would match "john 50 60 29" | 17:57 |
ignas | just as well | 17:57 |
ignas | and if it will fail because 6 has changed to 7 | 17:57 |
replaceafill | u would use the whole html? | 17:57 |
ignas | well - good luck finding out what has changed | 17:58 |
ignas | well - it's a table | 17:58 |
ignas | and you probably know the id of the table | 17:58 |
ignas | so you could use analyze.queryHTML() | 17:58 |
ignas | and xpath | 17:58 |
ignas | to filter out and print only the table itself | 17:58 |
ignas | for example | 17:58 |
replaceafill | wow, didnt know about analyze | 17:58 |
replaceafill | where does it come from? | 17:59 |
ignas | look up usages of queryHTML in schooltool code | 17:59 |
replaceafill | i mean, which package? | 17:59 |
ignas | it is in functional test globals | 17:59 |
ignas | by default | 17:59 |
ignas | so you just do: | 17:59 |
ignas | >>> print analyze.queryHTML('//div[@class="commendation"]', | 17:59 |
ignas | ... manager.contents)[0] | 17:59 |
ignas | in a functional test | 17:59 |
ignas | and you get all the divs that have class set to commendation | 17:59 |
ignas | well - just the first div actually | 18:00 |
replaceafill | cool, xpath | 18:00 |
replaceafill | like in selenium :) | 18:00 |
ignas | a bit more precise than blobbing everything with ... | 18:00 |
replaceafill | yes | 18:00 |
ignas | also gives a *LOT* less output | 18:00 |
ignas | when it fails | 18:00 |
replaceafill | cool! | 18:01 |
ignas | so at least limmiting it to the '//div[@id="content-body"]' | 18:01 |
ignas | part of the page | 18:01 |
ignas | strips all the menus | 18:01 |
ignas | from the output | 18:01 |
replaceafill | ignas, is it ok to fix the unique title issue using a prefix in the adapter/utility? | 18:04 |
ignas | hmm, don't really know | 18:04 |
replaceafill | kind of the way formlib does it | 18:04 |
ignas | aren't there other ways | 18:04 |
ignas | to identify an activity? | 18:04 |
ignas | ahh | 18:05 |
ignas | i guess not | 18:05 |
replaceafill | title, description | 18:05 |
replaceafill | category | 18:05 |
ignas | i mean like __name__ | 18:05 |
ignas | i guess not really | 18:05 |
replaceafill | Activity, Activity-2 | 18:05 |
ignas | still - can you "get" the activity using that thing | 18:06 |
ignas | you must be able to reliably convert the identifier string | 18:06 |
ignas | into an activity | 18:06 |
ignas | and back | 18:06 |
ignas | as I don't really know how activities are being accessed | 18:06 |
ignas | and how the constraint of different titles is being enforced | 18:07 |
ignas | titles are for presentation | 18:07 |
ignas | descriptions are for presentation either | 18:07 |
replaceafill | i guess right now the gradebook allows 2 act with the same title | 18:08 |
ignas | in perfect case external activity sources should allow you | 18:08 |
ignas | to add activities that are not persistent objects | 18:08 |
ignas | replaceafill: also cando might allow it, or some other plugin that adds activities | 18:08 |
ignas | so the activity source adapter for a section should be the one doing mapping between external activities and their ids | 18:09 |
ignas | so what you'd need as an actual ID | 18:09 |
ignas | would be the adapter/utility name + the id | 18:09 |
ignas | as 2 separate attributes | 18:09 |
ignas | i'd say | 18:09 |
ignas | (no a dot is not reliable, someone can call his adapter "schooltool.my.activities") | 18:10 |
ignas | or can name his activity "St. Christianology" | 18:10 |
replaceafill | :D | 18:10 |
replaceafill | true | 18:10 |
ignas | so to convert an activity ID into the actual external activity | 18:10 |
replaceafill | something like an intid? | 18:10 |
ignas | in a linked activity you would do | 18:10 |
ignas | something like that | 18:10 |
ignas | you have "activity_id" and "adapter_name" | 18:11 |
replaceafill | does st have an intid utility? | 18:11 |
ignas | yeah, but i'd not use it for this | 18:11 |
ignas | as you would enforce | 18:11 |
ignas | a constraint - all external activities must be persistent | 18:11 |
replaceafill | oh yes | 18:11 |
ignas | you do queryAdapter(section, IExternalActivities, name=adapter_name).getActivity(activity_id) | 18:11 |
ignas | to acquire the external activity | 18:12 |
ignas | IExternalActivity has an attribute | 18:12 |
ignas | ID | 18:12 |
ignas | that can be used to look it up | 18:12 |
ignas | also - external activity plugin writers | 18:12 |
ignas | must write a subscriber | 18:12 |
ignas | that removes all the linked activities | 18:13 |
replaceafill | how can that be enforced? | 18:13 |
ignas | it can't | 18:13 |
replaceafill | i mean the thirdparty writer to know about the subscriber | 18:13 |
ignas | but should be documented i'd guess | 18:13 |
replaceafill | ah ok | 18:13 |
ignas | that have adapter_name == my_adapter_name and activity_id == event.object.id | 18:13 |
ignas | linked activities removed | 18:14 |
ignas | from the database | 18:14 |
ignas | the slightly tricky case might be listing the activities in choice fields | 18:15 |
replaceafill | to filter them? | 18:15 |
ignas | though - you can either display multiple boxes (one for each provider) | 18:15 |
ignas | or you will have to play around with some clever code to generate double ids | 18:15 |
ignas | but still - as you control the ID generation | 18:15 |
ignas | (users don't see the ids) | 18:15 |
ignas | you can think of something | 18:16 |
replaceafill | writing down... | 18:16 |
ignas | can you explain me what and why the updategrades view does? | 18:17 |
replaceafill | it refreshes the grades of the linkedactivity | 18:18 |
ignas | i mean - why is it needed? | 18:18 |
replaceafill | from the cando skilldriver | 18:18 |
replaceafill | jelkner asked for a button to update the grades | 18:19 |
ignas | yeah, but shouldn't it be performed automatically? | 18:19 |
replaceafill | he didn't want them to be automatically refreshed | 18:19 |
ignas | ahhh | 18:19 |
ignas | so | 18:20 |
ignas | if someone deletes a skill driver | 18:20 |
ignas | you have following problems | 18:20 |
ignas | LinkedActivity | 18:20 |
ignas | returns None | 18:21 |
ignas | when you try to get it's activity | 18:21 |
ignas | you have an evaluation in your gradebook | 18:21 |
ignas | that has not been removed | 18:21 |
ignas | I am interested in how it would work | 18:21 |
ignas | in the actual gradebook grading views | 18:22 |
replaceafill | the subscriber should fix that, right? | 18:24 |
ignas | yeah | 18:24 |
ignas | i mean - you have a plugin that provides external activities for cando | 18:24 |
ignas | so i'd assume you can write a functional test | 18:24 |
ignas | that looks at what actually happens | 18:24 |
ignas | if you "add" linked activity that points at cando | 18:25 |
ignas | and then grade it | 18:25 |
ignas | update grades | 18:25 |
ignas | then remove the skilldriver | 18:25 |
ignas | then refresh the gradebook views | 18:25 |
ignas | try grading it in the gradebook | 18:25 |
ignas | try clicking it and stuff | 18:25 |
replaceafill | since the linked activity couldnt find the external activity it should raise an lookuperror | 18:27 |
replaceafill | and fail miserably :) | 18:27 |
ignas | yeah, and if it not fails - what happens to the code that calculates averages? displays grading tables? | 18:27 |
ignas | can you do identical titles among 2 activities in cando | 18:28 |
ignas | what happens if you change a title | 18:28 |
ignas | and why is the view called AddExternalActivity | 18:29 |
ignas | if it is adding LinkedActivities | 18:29 |
replaceafill | yes! | 18:29 |
ignas | i mean - external activities are being provided by the utility | 18:29 |
replaceafill | definitely | 18:29 |
replaceafill | i got with the "New External Activity" label | 18:30 |
replaceafill | but that should be AddLinkedActivity | 18:30 |
ignas | well - the label can stay that way, for a user it is "add external activity" thing | 18:32 |
ignas | maybe "register external activity" | 18:32 |
ignas | or something like that | 18:32 |
ignas | but still the view should match the class it is adding | 18:32 |
replaceafill | yes | 18:32 |
ignas | so yeah if you would bzr branch trunk | 18:32 |
ignas | apply the changes you did in some way | 18:33 |
ignas | bzr diff -r 1.. | 18:33 |
ignas | in your branch | 18:33 |
ignas | the one that you did | 18:33 |
ignas | gives a full diff | 18:33 |
ignas | that you should be able to bzr patch | 18:33 |
ignas | onto your new branch | 18:33 |
ignas | and then fix at least some of the stuff | 18:33 |
ignas | it would be nice | 18:33 |
replaceafill | ok will do it | 18:35 |
replaceafill | the id thing is the one that still bothers me | 18:35 |
replaceafill | how to get a unique one | 18:35 |
ignas | it should be taken care of by the utility/adapter really | 18:36 |
ignas | so in your test | 18:36 |
ignas | it should be performed by your stubs | 18:36 |
ignas | and in cando | 18:36 |
ignas | by cando | 18:36 |
ignas | cando for example | 18:36 |
ignas | can use IntId | 18:36 |
ignas | utility | 18:36 |
ignas | stubs can do whatever they want ;) | 18:36 |
replaceafill | yes because skilldrivers are persistent | 18:36 |
replaceafill | thanks ignas, i will work on all of this | 18:37 |
ignas | as long as a pair of adapter_name + activity ID | 18:37 |
ignas | are enough to get an external activity | 18:37 |
ignas | by calling queryUtility(section, IExternalActivities, name=name).get(activity_id) | 18:38 |
ignas | it should work | 18:38 |
ignas | so in cando case - the get() method would just perform and indID lookup | 18:38 |
aelkner | replaceafill: as you take note of ignas' suggestions | 19:03 |
aelkner | may i suggest that you save the bullet-proofing for the last step | 19:03 |
aelkner | by that i mean, the considerations that ignas outlined for avoiding conflicts with duplicate external activities | 19:04 |
aelkner | and get the essential parts working enough that we can deploy to jelkner's machine on thursday | 19:04 |
ignas | aelkner: problem | 19:04 |
ignas | bulletproofing | 19:05 |
ignas | is touching the ID part | 19:05 |
ignas | having titles as IDs is very bad | 19:05 |
ignas | and if you don't change it | 19:05 |
ignas | and deploy it | 19:05 |
ignas | changing it to something/anything else | 19:05 |
ignas | will be very difficult | 19:05 |
ignas | and require schooltool.gradebook + cando evolution script | 19:05 |
ignas | frankly | 19:06 |
ignas | you can do it well with utility | 19:06 |
ignas | instead of an adapter | 19:06 |
ignas | as that is code | 19:06 |
ignas | so if anything should be postponed - it's the utility stuff, not the id stuff | 19:06 |
aelkner | ok, sounds fine | 19:07 |
aelkner | just want us to realize that we don't have more than one client at the moment | 19:07 |
aelkner | and jelkner wants something on his desk by thursday | 19:07 |
aelkner | so in the spirit of yagni, can we please not worry about all test cases before then | 19:08 |
ignas | aelkner: i am not really sure anyone will be spending any time on this feature after it's merged, unless it breaks ;) | 19:08 |
ignas | and i am trying to prevent the "breaks" part | 19:08 |
aelkner | i'm not suggesting that we merge anything before it is ready | 19:08 |
aelkner | i'm just suggesting that replaceafill focus on getting the code to function for jelkner first | 19:09 |
ignas | if it was automatic external activities without the intermediate persistent layer it would be easier to prototype... | 19:09 |
ignas | but doing jelkner specific evolution might be tricky | 19:09 |
replaceafill | afk, bath | 19:10 |
aelkner | ignas: i agree that replaceafill should address the issues that would help avoid the need for evolution | 19:11 |
aelkner | like handling the id issue | 19:11 |
aelkner | i'm just trying to get something to jelkner by thursday so that he can demonstrate to whomever he wishes | 19:13 |
ignas | if it is for demonstration | 19:13 |
ignas | that i'd say current code should work | 19:13 |
ignas | as long as you destroy the Data.fs | 19:13 |
ignas | after the demo | 19:13 |
ignas | ;) | 19:13 |
aelkner | no, i'm not suggesting that | 19:13 |
aelkner | the id issue whold be resolved by thursday | 19:13 |
aelkner | but things like the subscriber | 19:14 |
aelkner | that handles deleting the linked activity | 19:14 |
aelkner | is not needed for any demo | 19:14 |
aelkner | jelkner can just avoid deleting a skill driver | 19:14 |
ignas | yeah | 19:14 |
aelkner | that's all i'm saying | 19:14 |
ignas | with one user you can live with it | 19:14 |
aelkner | but we'll make sure to get everything right before we request the merge | 19:15 |
ignas | ok, as long as the right code gets merged - it's you who will be resolving problems with the wrong one ;) | 19:16 |
ignas | also it's you who would be suffering if you would not complete it for thursday I guess | 19:16 |
*** ignas has quit IRC | 19:39 | |
*** alga has quit IRC | 20:00 | |
*** mgedmin has quit IRC | 20:00 | |
*** alga has joined #SchoolTool | 20:12 | |
aelkner | replaceafill: so when ignas says: | 20:52 |
aelkner | you do queryAdapter(section, IExternalActivities, name=adapter_name).getActivity(activity_id) | 20:53 |
aelkner | he's talking about using the object id instead of the title | 20:53 |
aelkner | becuase the user can change the title | 20:53 |
replaceafill | yes | 20:53 |
aelkner | but the object id stays the same | 20:53 |
aelkner | now even though zodb objects are python objects | 20:54 |
aelkner | i wouldn't rely on the python method, id(objecct) | 20:54 |
aelkner | because i'm not sure if that id stays the same from run to run | 20:55 |
aelkner | however, i believe IKeyReference(object) returns an id that is persistent | 20:55 |
aelkner | look at gradebook.,py for an example of its use | 20:56 |
replaceafill | ok | 20:56 |
aelkner | yes, the section's id is obtained using that construct | 20:58 |
aelkner | and that is persisted to the person object's annotation | 20:58 |
aelkner | so it can be relied upon | 20:58 |
replaceafill | but ikeyreference depend on the object being persistent, right? | 20:58 |
aelkner | the skilldriver is | 20:59 |
aelkner | so even though external activities aren't | 20:59 |
aelkner | they act as a proxy to the skilldriver which is | 20:59 |
replaceafill | yes | 21:00 |
aelkner | so | 21:00 |
aelkner | you should change the linked activity to not use title | 21:01 |
aelkner | but use this id instead | 21:01 |
aelkner | but also, as ignas indicated | 21:01 |
aelkner | the name of the adapter | 21:01 |
replaceafill | should be part of the id | 21:01 |
aelkner | no, they are two pieces of information | 21:02 |
aelkner | again, look at the queryAdapter call | 21:02 |
aelkner | ther is adapter_name and activity_id | 21:02 |
aelkner | both pieces need to be stored in the linked activity | 21:03 |
aelkner | so the user that creates the linked activity | 21:03 |
aelkner | will need to choose both | 21:03 |
aelkner | a pulldown with cando and other plugin in it | 21:04 |
aelkner | then a pulldown with the list of skilldrivers in the case of cando | 21:04 |
aelkner | and the titles will be tied to the ids | 21:04 |
replaceafill | these external activities are from this source | 21:04 |
replaceafill | these other external activities are from this other source | 21:05 |
aelkner | right | 21:05 |
replaceafill | well, will work on that | 21:06 |
aelkner | if it's not ready by thursday, don't worry | 21:08 |
replaceafill | really? | 21:08 |
aelkner | you couldn't have anticipated this problem | 21:08 |
aelkner | you'll do your best | 21:08 |
aelkner | and i'm sure jelkner will understand | 21:08 |
replaceafill | cool | 21:09 |
replaceafill | i'll work the fastest i can though | 21:09 |
aelkner | best of luck with it | 21:10 |
replaceafill | thanks man :) | 21:10 |
replaceafill | we will need js for that UI, right? | 21:10 |
replaceafill | to select the external activities according to their source | 21:11 |
aelkner | hm | 21:11 |
aelkner | it's true that the server can't know what the user chooses for source | 21:13 |
aelkner | when it delivers the vocabulary for the activiies | 21:13 |
aelkner | so i guess you would need js to limit the choices to the user according to what is chosen for the source | 21:14 |
replaceafill | ok | 21:15 |
aelkner | so if no source is chosen, the list of activities should be empty | 21:15 |
replaceafill | yes | 21:15 |
aelkner | if there is only one source, as will be the case for jelkner | 21:16 |
aelkner | then that choice is fixed and the list of activites can already be set | 21:17 |
aelkner | for thursday | 21:17 |
aelkner | you could work around the js part | 21:17 |
aelkner | just assume that there is only one choice | 21:17 |
aelkner | and get that working | 21:18 |
replaceafill | ok | 21:18 |
aelkner | the key is getting something to jelkner | 21:18 |
aelkner | but also | 21:18 |
aelkner | having the right data structure so that we won't need to evolve | 21:18 |
aelkner | so i think you can arrange that without having ot worry about the js part yet | 21:19 |
replaceafill | ok | 21:19 |
replaceafill | well, gonna get something to eat | 21:20 |
replaceafill | brb | 21:20 |
*** Aiste has quit IRC | 21:51 | |
*** fsufitch has quit IRC | 21:51 | |
*** th1a has quit IRC | 21:51 | |
*** aelkner has quit IRC | 21:51 | |
*** jstraw has quit IRC | 21:51 | |
*** lisppaste5 has quit IRC | 21:51 | |
*** alga has quit IRC | 21:51 | |
*** replaceafill has quit IRC | 21:51 | |
*** spowers has quit IRC | 21:51 | |
*** jfroche has quit IRC | 21:52 | |
*** wgrant has quit IRC | 21:52 | |
*** jfroche has joined #schooltool | 22:10 | |
*** spowers has joined #schooltool | 22:10 | |
*** lisppaste5 has joined #schooltool | 22:10 | |
*** Aiste has joined #schooltool | 22:10 | |
*** aelkner has joined #schooltool | 22:10 | |
*** jstraw has joined #schooltool | 22:10 | |
*** wgrant has joined #schooltool | 22:10 | |
*** th1a has joined #schooltool | 22:10 | |
*** replaceafill has joined #schooltool | 22:10 | |
*** alga has joined #schooltool | 22:10 | |
*** fsufitch has joined #schooltool | 22:10 |
Generated by irclog2html.py 2.15.1 by Marius Gedminas - find it at mg.pov.lt!