[13:04]  * shadeslayer is excited about tomorrow
[14:13] <cjohnston> tomorrow?
[14:15] <shadeslayer> cjohnston: eh?
[14:15] <shadeslayer> cjohnston: oh ill be giving a packaging session ;)
[14:15] <cjohnston> gotcha
[14:16] <shadeslayer> :D
[14:16] <Pendulum> cjohnston: and hggdh is giving a bug q&a
[14:16] <cjohnston> I knew about hggdh's
[14:16] <cjohnston> I knew about the PPA thing too..
[14:16] <cjohnston> just fergot about it
[14:16] <Pendulum> heh
[14:17] <cjohnston> since I put it on the calendar..
[14:17] <cjohnston> lol
[14:17] <shadeslayer> :P
[14:17] <cjohnston> shadeslayer: PM...
[14:17] <shadeslayer> im just writing notes write now on it
[15:18] <hggdh> what happens with me?
[15:32] <cjohnston> hggdh: you are doing a bug q&a later
[15:32] <hggdh> cjohnston: yes, indeed. Adn you wanted to talk with me
[15:33] <cjohnston> yup.. Got a few?
[15:35] <hggdh> yes
[15:35] <cjohnston> PM hggdh
[17:01] <mhall119|work> hello and good afternoon
[17:01] <mhall119|work> today we're going to do a double class, since I had to cancel yesterday
[17:02] <mhall119|work> for those of you who misses yesterday, logs from the first two sessions can be found here: https://launchpad.net/classroom-scheduler
[17:02] <mhall119|work> A quick recap of what we covered yesterday...
[17:03] <mhall119|work> we made Django Models for Session (a single online class), Course (a collection of classes), and LernidSession (a derivative of Session, that includes a URL for slides)
[17:04] <mhall119|work> we also configured the Django Admin application so we can view our Models and create instances of them in the database
[17:04] <mhall119|work> today we finally get to the rewarding part of Django, creating the views and forms that will let our users interact with our models
[17:05] <mhall119|work> now, before we begin, I made some changes to the application that are going to require you to modify your database
[17:06] <mhall119|work> If you go to https://wiki.ubuntu.com/mhall119/classes/LearningDjango and scroll down to the section for Day 3
[17:06] <mhall119|work> you will see my note
[17:06] <mhall119|work> I'll briefly explain what changes I made and why
[17:07] <mhall119|work> "class" is a python keyword for defining, what else, classes
[17:07] <mhall119|work> by calling my application 'class' initially, I caused a conflict with this keyword when I tried to use it as a namescape in python
[17:08] <mhall119|work> so, I renamed the application to "classroom" to avoid the conflict
[17:08] <mhall119|work> however, when django created tables in the database, it defaults to naming them "application_model"
[17:08] <mhall119|work> by changing the name of the application, I also changed the name of the tables it will use
[17:09] <mhall119|work> so, once you run "bzr pull -r tag:day3.1"
[17:10] <mhall119|work> you will need to either re-run "python manage.py syncdb", or just download the classroom_scheduler.db I uploaded here: https://wiki.ubuntu.com/mhall119/classes/LearningDjango?action=AttachFile&do=view&target=classroom_scheduler.db
[17:10] <mhall119|work> I'll give a few minutes for everyone to get their local copy updated to day3.1
[17:11] <mhall119|work> ask a QUESTION: if you have trouble with this
[17:12] <mhall119|work> if you update your old database, go into the admin and create a session and course for testing
[17:13] <mhall119|work> alright, time to get started
[17:13] <mhall119|work> day3.1 brought in several updates
[17:14] <mhall119|work> it creates a new view method "schedule_view"
[17:14] <mhall119|work> along with templates/schedule_view.html
[17:14] <mhall119|work> and wired them up to the url /schedule/
[17:15] <mhall119|work> if you again run "python manage.py runserver" you should see something like this: http://www.growingupfree.org:8001/schedule/
[17:16] <mhall119|work> let's look into classroom/views.py
[17:16] <mhall119|work> as you can see, schedule_view is very simple
[17:17] <mhall119|work> Session.objects.filter() returns a list of Session objects from the database
[17:17] <mhall119|work> you can add key/value pairs to filter() to restrict what is returned, we'll see that in action later
[17:17] <mhall119|work> next it creates a Context instance, and adds the list of sessions to it
[17:18] <mhall119|work> it then calls render_to_response, passing it the name of the template and the context
[17:18] <mhall119|work> render_to_response is a utility method that will render the template into an HttpResponse object for us
[17:19] <mhall119|work> now look at classroom/templates/schedule_view.html
[17:19] <mhall119|work> Django templates are HTML with additional markup
[17:20] <mhall119|work> {% %} blocks are "tags" that perform logic operations
[17:20] <mhall119|work> {{ }} blocks will inline the value of the contained expression into the HTML
[17:21] <mhall119|work> Django supplies many built in tags for you to use, and you can see we are using the {% for value in list %} tag here to iterate through our list of sessions
[17:21] <mhall119|work> you'll also notice that the {{ }} blocks let you properties of a variable as well
[17:22] <mhall119|work> so {{ session.course.title }} will try to get the title of the course this session is a part of
[17:22] <mhall119|work> any questions so far?
[17:23] <mhall119|work> alright, moving on
[17:24] <mhall119|work> run: "bzr pull -r tag:day3.2"
[17:24] <mhall119|work> then "bzr diff -r tag:day3.1"
[17:25] <mhall119|work> here you can see that we are using the {% if %}{% else %}{% endif %} blocks
[17:25] <mhall119|work> then do exactly what you would expect
[17:26] <mhall119|work> {% if value %} will be true if value has a non-false value in python
[17:26] <mhall119|work> so, None, 0 and empty lists will execute the {% else %} block
[17:26] <mhall119|work> {% else %} is optional
[17:27] <mhall119|work> now we are currently returning the datatime as python's default string representation
[17:28] <mhall119|work> but if you look at the schedule on the wiki https://wiki.ubuntu.com/Classroom you see that the date and time are split into 2 columns
[17:28] <mhall119|work> if you run "bzr pull -r tag:day3.3", then "bzr diff -r tag:day3.2"
[17:29] <mhall119|work> you will see that i split it into 2 columns, and use template "filters" to format what is displayed
[17:29] <mhall119|work> again, Django provides a number of built in filters such as "date"
[17:30] <mhall119|work> filters use the syntax: {{variable|filter:"extra"}}
[17:30] <mhall119|work> the :"extra" is optional, not all filters need it
[17:31] <mhall119|work> filters are used to act on the value of a variable, and return a different result
[17:31] <mhall119|work> Now you should be seeing something like this: http://www.growingupfree.org:8001/schedule/
[17:31] <mhall119|work> we're starting to look a lot more like the wiki schedule now
[17:32] <ClassBot> hemanth asked: can the filtering of time and date be done as per the clients local time?
[17:33] <mhall119|work> I don't think so, if store your time in UTC, you will have to change it to local time before using the filter
[17:33] <mhall119|work> now, looking back at https://wiki.ubuntu.com/Classroom#Schedule
[17:34] <mhall119|work> you will see that their times are links to an external site that does give times in several locations
[17:34] <ClassBot> hemanth asked: can't we get the location of the client and do UTC +/- ?
[17:35] <mhall119|work> yes, and in fact python has a helpful method of doing this in datetime.timedelta
[17:35] <mhall119|work> alright, so next we want to turn out time display into a link
[17:36] <mhall119|work> we can do this in our template itself, but this is used all the time within the Ubuntu wiki when times are displayed, and we don't really want to copy/paste it into every template we make
[17:36] <mhall119|work> instead, we're going to make our own template filter to do it
[17:36] <mhall119|work> run "bzr pull -r tag:day3.4"
[17:37] <mhall119|work> and then "bzr diff -r tag:day3.3"
[17:37] <mhall119|work> you will see the new file classroom/templatetags/scheduler_tools.py
[17:38] <mhall119|work> this a file that will contain any number of custom tags and filters
[17:38] <mhall119|work> we use {% load scheduler_tools %} to include them in our schedule_view.html template
[17:39] <mhall119|work> then we can get the URL for our link by simply using {{session.start_time|timelink}}
[17:39] <mhall119|work> if you look at classroom/templatetags/scheduler_tools.py, you see that filters are pretty easy to write
[17:40] <mhall119|work> they are basically a function that takes the associated variable as an argument (and optionally any "extra" values)
[17:40] <mhall119|work> and returns a value to be inlined into the HTML
[17:41] <mhall119|work> @register.filter is a python decorator that registers your filter with Django when {% load %} is called on the file
[17:41] <mhall119|work> now, just as Filters are exceptionally easy to write, Tags are quite hard
[17:41] <mhall119|work> which is why I am not covering them in this series
[17:42] <mhall119|work> but if you are curious, you can view them in the Django source files to see how they are implemented
[17:42] <mhall119|work> any questions at this point?
[17:43] <mhall119|work> anyone need a break before we get started on the next hour?
[17:44] <ClassBot> lsteeger asked: yes. how about 5 mins?
[17:45] <mhall119|work> okay, we'll take a short break then
[17:52] <mhall119|work> okay, is everyone back?
[17:53] <mhall119|work> alright
[17:53] <mhall119|work> time to get into forms!
[17:54] <mhall119|work> much like a model, a Form is a collection of Field instances that map python values to HTTP parameters
[17:55] <mhall119|work> that makes it easy to pass the contents of request.GET or request.POST to a Form, and get the right python data types out
[17:55] <mhall119|work> form fields will validate their own content, and raise an exception of it can not be converted into the right python data type
[17:56] <mhall119|work> for example, if you pass "abc" to an IntegerField, it will raise an exception
[17:57] <mhall119|work> you call form.is_valid() to determine if all the fields in a form contain well formatted values
[17:57] <mhall119|work> you can then get the corresponding python data type from form.cleaned_data[]
[17:58] <mhall119|work> Django form fields also contain widgets that are used to create the HTML necessary to build the form
[17:58] <mhall119|work> so you don't even have to worry about that
[17:59] <mhall119|work> a full listing of Django's form fields can be seen here: http://docs.djangoproject.com/en/1.1/ref/forms/fields/#ref-forms-fields
[17:59] <mhall119|work> however, you won't actually have to explicitly use them all the time
[18:01] <mhall119|work> Django provides a helpful ModelForm type, that will create a Form class with all the fields
[18:01] <mhall119|work> in your Model
[18:01] <mhall119|work> run "bzr pull -r tag:day3.6"
[18:02] <mhall119|work> and "bzr diff -r tag:day3.4"
[18:02] <mhall119|work> (yes, day3.5 is missing)
[18:02] <mhall119|work> now, you will see a new file: classroom/forms.py
[18:03] <mhall119|work> this file contains 2 very small classes, SessionForm and CourseForm, both of which extend Django's ModelForm
[18:03] <mhall119|work> you will also notice that, just like your Models, it contains an internal Meta class
[18:03] <mhall119|work> in this class, we specify which of our Model definitions we want this Form class to use
[18:04] <mhall119|work> and that's really all we need
[18:05] <mhall119|work> we can now call form = SessionForm(request.POST)
[18:06] <mhall119|work> we can also bind an instance of our model to the form: form = SessionForm(instance=mysession, data=request.POST)
[18:06] <mhall119|work> if we have a bound instance, we can call form.save() to apply all of the HTTP values to the instance, and save the instance to the database
[18:06] <mhall119|work> this really saves a lot of boiler plate code you would need to write in other frameworks
[18:07] <mhall119|work> we can also pass the bound form instance to a template for display
[18:07] <mhall119|work> run "bzr pull -r tag:day3.7"
[18:08] <mhall119|work> now you will have 2 new files, classroom/templates/session_view.html and classroom/templates/course_view.html
[18:09] <mhall119|work> they are largely the same, so we'll just look at classroom/templates/session_view.html for now
[18:09] <mhall119|work> by default, forms will render themselves as a series of table rows and cells
[18:10] <mhall119|work> you can also use {{form.as_p}} or {{form.as_ul}} to have it render as a series of <p> or <ul><li> tags
[18:11] <mhall119|work> notice that it does not create the <form> or <table> tags itself, this makes it easy to add thing before or after the form fields
[18:11] <mhall119|work> such as submit and reset buttons
[18:11] <mhall119|work> now if you look at classroom/views.py you will see 2 new view functions
[18:12] <mhall119|work> session_view and course_view
[18:12] <mhall119|work> both take an additional id argument
[18:12] <mhall119|work> again, the view functionality is similar between them, so we'll only look at session_view
[18:13] <mhall119|work> first we determine if the session_id is 0, this lets us know if we are creating a new session, or modifying an existing one
[18:13] <mhall119|work> if it's 0, we just create an empty Session() instance
[18:14] <mhall119|work> otherwise, we use the helper method get_object_or_404 to see if a session by that id exists in the database
[18:14] <mhall119|work> if it doesn't exist, this method will raise an exception, and Django will forward the request to an error page saying that the Session for that id can't be found
[18:14] <mhall119|work> next, we check to see if this is an HTTP POST request.
[18:15] <mhall119|work> if it is a POST, then we need to process the submitted values from our form
[18:15] <mhall119|work> we create a new SessionForm, bound to both the Session instance we created and the POST data that was submitted
[18:16] <mhall119|work> calling is_valid() will return true if all of the form's fields contain good data
[18:16] <mhall119|work> if it's all good, we call save() to apply the data to the record in the database
[18:16] <mhall119|work> if it's not a POST request, we are just going to display the form to the user
[18:17] <mhall119|work> we create a SessionForm that is only bound to the session instance
[18:17] <mhall119|work> and add that to a Context object for our template
[18:17] <mhall119|work> and then call render_to_response again
[18:18] <mhall119|work> finally, let's look at urls.py
[18:18] <mhall119|work> we added 2 new entries there, for session_view and course_view
[18:19] <mhall119|work> these use a slightly more complex url pattern than we've previously used, so lets take a closer look at them
[18:19] <mhall119|work> r'^session/(?P<session_id>\d+)'
[18:19] <mhall119|work> this will match /session/#, where # can be any digit
[18:19] <mhall119|work> or multiple digists
[18:19] <mhall119|work> as long as it's a number
[18:19] <mhall119|work> 123 works
[18:19] <mhall119|work> 12abc doesn't
[18:20] <mhall119|work> it then assigns the matching value to session_id, which is passed to the view function
[18:20] <mhall119|work> so, to view session #1, use the url /session/1
[18:20] <mhall119|work> to view session #2, use /session/2
[18:20] <mhall119|work> and to get a form for creating a new session, use /session/0
[18:20] <mhall119|work> that is because our view is treating session_id==0 as a special case
[18:21] <ClassBot> ryanprior45 asked: where do I type /session/1 ?
[18:21] <mhall119|work> sorry, that would be at the end of your URL, like http://127.0.0.1:8000/session/1
[18:22] <ClassBot> hemanth asked: does dw+ match words and digit, is it the same logic?
[18:22] <mhall119|work> any valid regular expression will work
[18:23] <mhall119|work> \d\w+ would work
[18:23] <mhall119|work> to match 1abc
[18:23] <mhall119|work> for example
[18:24] <mhall119|work> we also added links to the schedule view to take you to the correct URL
[18:24] <mhall119|work> it should look something like this: http://www.growingupfree.org:8001/schedule/
[18:25] <mhall119|work> if you look at classroom/templates/schedule_view.html you will see how we're doing this
[18:26] <mhall119|work> now, since views are mapped to url patterns in urls.py, you won't necessarily know what the URL pattern will look like when you're writing your templates and views
[18:27] <mhall119|work> to accomodate this, Django has a powerful reverse-lookup ability that when given the name of a view, can give you a URL that it is mapped to
[18:28] <mhall119|work> so we use {% url session_view session.id %} to lookup a URL pattern named "session_view" that takes a single argument
[18:28] <mhall119|work> Django then locates the URL pattern, and inlines the argument value you passed
[18:28] <mhall119|work> so {% url session_view session.id %} will return /session/1 if session.id==1
[18:29] <mhall119|work> if we wanted to change you URL pattern to '^classroom/session/(?P<session_id>\d+)'
[18:29] <mhall119|work> then {% url %} would return /classroom/session/1
[18:30] <mhall119|work> this makes Django applications very portable, as you can write them without knowing (or caring) how the urls will ultimately be mapped in a given project
[18:30] <mhall119|work> any other questions on what we just covered?
[18:31] <mhall119|work> alright
[18:32] <mhall119|work> now, while Django is smart enough to validate the format of data, it's doesn't know enough to determine if the value makes logical sense
[18:32] <mhall119|work> for example, when adding a new Session, it wouldn't make sense to create one with an end_time in the past
[18:33] <mhall119|work> you can do this in your view, but if the validation is something that should always exist, it's best to add it to the Form
[18:33] <mhall119|work> that way, is_valid() will run your custom validations, and return false if any one of them raises an exception
[18:34] <mhall119|work> run "bzr pull -r tag:day3.8.1"
[18:34] <mhall119|work> now if you look at classroom/forms.py you will see that I've added a method to our Form classes
[18:35] <mhall119|work> Django looks for methods named "clean_$fieldname" for each $fieldname in the form
[18:35] <mhall119|work> since we are using ModelForms, that means for every field name in the Model itself
[18:36] <mhall119|work> so when it is validating the format of the data in 'end_time', it will look for a method called 'clean_end_time', and call it for the final cleaned value
[18:37] <mhall119|work> in there, I retrieve the current clean value for the field, and if the Session is new, check that the 'end_time' value exists sometime in the future
[18:38] <mhall119|work> if it doesn't, an exception will be raised.  is_valid will return false, and the user will be directed back to the form page, only now the text of the exception will be displayed above the 'end_time' field
[18:38] <mhall119|work> now, that's an easy way for validating an field's value by itself
[18:39] <mhall119|work> but often times you need to validate a field's value when compared to another field's value
[18:39] <mhall119|work> for example, it wouldn't make sense for a session's end_time to come before it's start_time
[18:40] <mhall119|work> to do that, we need to implement a 'clean()' method on the form, that will be called after all the 'clean_$fieldname' methods are called
[18:40] <mhall119|work> run "bzr pull -r tag:day3.8.2"
[18:41] <mhall119|work> and "bzr diff -r tag:day3.8.1"
[18:41] <mhall119|work> now you will see the clean() method on each of our Form classes
[18:41] <mhall119|work> the first thing I do is get the values for start_time and end_time
[18:42] <mhall119|work> notice that I'm using self.cleaned_data.get('start_time'), rather than self.cleaned_data['start_time']
[18:42] <mhall119|work> that's because of 'start_time' failed validation, it won't be added to self.cleaned_data, but Django will continue to call all clean and clean_fieldname methods anyway
[18:43] <mhall119|work> so we need to account for that field not existing in cleaned_data
[18:43] <mhall119|work> calling cleaned_data.get('start_time') will return None if it doesn't exist
[18:43] <mhall119|work> calling cleaned_data['start_time'] will raise a KeyError
[18:44] <mhall119|work> next I check that both 'start_time' and 'end_time' are not None, and then if 'end_time' comes before 'start_time', raise an exception
[18:45] <mhall119|work> don't forget to return self.cleaned_data, otherwise you won't be able to use it in your view, or when calling form.save
[18:46] <mhall119|work> any questions so far on forms validation?
[18:47] <mhall119|work> alright
[18:47] <mhall119|work> in tag:day3.9 I just added an extra table to the schedule view for listing courses, there isn't really anything in it that we haven't already discussed
[18:48] <mhall119|work> by now, we can see our data, we can add data, and we can modify data
[18:48] <mhall119|work> but, we can't delete it!
[18:48] <mhall119|work> it's time to implement that
[18:48] <mhall119|work> run "bzr pull -r tag:day3.10"
[18:49] <mhall119|work> you will get the new files classroom/templates/session_delete.html and classroom/templates/course_delete.html
[18:49] <mhall119|work> you will also get 2 new view fuctions in classroom/views.py
[18:49] <mhall119|work> let's look at those first
[18:50] <mhall119|work> since we don't need to think about new (unsaved) records when deleting, we just call get_object_or_404 to retrieve the instance of the record we want to delete
[18:51] <mhall119|work> now, it's polite to double check that the user wanted to actually delete something
[18:51] <mhall119|work> it's also good policy to not delete things on a GET request
[18:52] <mhall119|work> so, if it is a GET request, we first show them a page (course_delete.html or session_delete.html) to warn them about what they are about to do
[18:52] <mhall119|work> this page builds a <form> that will POST back to the view if the user confirms the deleting by clicking the submit button
[18:53] <mhall119|work> when the view sees a POST method, it will then delete the instance from the database, and redirect the user back to the schedule view
[18:53] <mhall119|work> if you look at the view templates for Session and Course, you will see the link added to the bottom to delete that instance
[18:54] <mhall119|work> by now your application should look like this: http://www.growingupfree.org:8001/schedule/
[18:54] <mhall119|work> you can add, edit and delete your sessions and courses to your heart's content
[18:54] <mhall119|work> and in only 4 hours time too!
[18:55] <mhall119|work> now, there is still much to implement, like access control, a better looking template, etc
[18:56] <mhall119|work> and I plan to offer more classes in this series to cover those topics after Ubuntu Opportunistic Developer Week (all of next week)
[18:56] <mhall119|work> this is also a live project: https://launchpad.net/classroom-scheduler
[18:56] <mhall119|work> so development will certainly continue on it, even after these classes are over
[18:57] <mhall119|work> well, my time is almost over, are there any questions on today's classes, or any previous ones?
[18:59] <mhall119|work> any suggestions or wants for future classes?
[18:59] <mhall119|work> okay, well the IRC logs are linked on the launchpad page now
[19:00] <mhall119|work> and as always, you can email me if you have any other questions; mhall119@ubuntu.com
[19:00] <ClassBot> hemanth asked: pre preparation list for the class? {suggestion}
[19:00] <mhall119|work> I only had a basic syllabus outline this time
[19:01] <mhall119|work> I will try to prepare a list of required apps to have installed before future classes
[19:01] <mhall119|work> is that what you were asking for?
[19:01] <malev> mhall119|work: I'm sorry, where do you say are the logs?
[19:01] <mhall119|work> https://launchpad.net/classroom-scheduler has links to all 4 hours
[19:01] <mhall119|work> hemanth: I will make sure I have better setup material next time
[19:02] <malev> mhall119|work: and.. is there a wiki?
[19:02] <hemanth> mhall119|work, ok :)
[19:02] <mhall119|work> now you guys have a working, useful Django application, start hacking on your own now!
[19:02] <mhall119|work> malev: only a syllabus outline: https://wiki.ubuntu.com/mhall119/classes/LearningDjango
[19:03] <malev> mhall119|work: cool!! thanks!