Generating timetables with Python

I hope I’ll be going next semester to Germany as an Erasmus student, and I’m now struggling with the Learning Agreement, courses, and timetables. In particular, there are a lot of interesting courses being offered by my guest university, but there are many factors to take into account when deciding what course you’re going to take, and one of them is timetable compatibility.

So, after a lot of struggle with the university’s website and trying to find some timetables, I’ve thought of making a timetable where I’ll put all the courses I’m interested in, so that I can see which ones are compatible with each other, i.e., which ones don’t overlap.

But the timetable has time slots of 15 minutes, from 8:00 to 18:15, and, of course, I’m naturally lazy. I don’t want to create a spreadsheet with as many rows as the 15-minutes-slots there are in the timetable!

And here’s when Python comes to the rescue!

I’ve coded a Python class that represents time, and which has methods for addition and logical comparison:

class Time:
    def __init__(self, hour, minutes):
        self.hour = hour
        self.minutes = minutes
    
    def normalize(self):
        hour = self.hour
        minutes = self.minutes
        
        quotient = minutes / 60
        if quotient > 0:
            hour += quotient
            minutes = minutes % 60
        
        self.hour = hour
        self.minutes = minutes
        
        return self
    
    def __add__(self, t):
        """add two times (sum)"""
        hour = self.hour + t.hour
        minutes = self.minutes + t.minutes
        res = Time(hour, minutes)
        res.normalize()
        return res
    
    def __mul__(self, k):
        """multiply a time and an integer constant k (product)"""
        hour = self.hour * k
        minutes = self.minutes * k
        res = Time(hour, minutes)
        res.normalize()
        return res
    
    def __lt__(self, t):
        """less than"""
        if self.hour < t.hour or (self.hour == t.hour and self.minutes < t.minutes):
            return True
        else:
            return False
    
    def __eq__(self, t):
        """equal"""
        if self.hour == t.hour and self.minutes == t.minutes:
            return True
        else:
            return False
    
    def __le__(self, t):
        """less or equal"""
        return self < t or self == t
    
    def __gt__(self, t):
        """greater than"""
        return not self <= t
    
    def __ge__(self, t):
        """greater or equal"""
        return self > t or self == t
    
    def __ne__(self, t):
        """not equal"""
        return not self == t
    
    def __str__(self):
        hour = fill(str(self.hour), 2, '0')
        minutes = fill(str(self.minutes), 2, '0')
        return '%s:%s' % (hour, minutes)

I’ve implemented all the basic logical operators for comparison, because they’re very easy to implement, but we won’t be needing all of them.

In addition, I’ve also coded the __str__ method, which allows you to convert Time objects into Python strings by just using the str() function. This method needs a function called fill, which is intended to pad or fill a string with some character, so that the string reaches a given maximum size. That is, for example, when you have a string '8' that you want to be 2 characters long; you would fill it with zeroes until you reach that length: '08'. The code for this function is pretty simple:

def fill(s, size, c=' ', position='before'):
    """s: string; c: char"""
    if position == 'before':
        s = c * (size - len(s)) + s
    elif position == 'after':
        s += c * (size - len(s))
    return s

And, finally, this is the function to generate the time slots from a start time up to a given end time by some time increment:

def generate_timetable(start_time, interval=Time(0, 15), times=5, end_time=None):
    timetable = []
    
    if end_time is None:
        end_time = start_time + interval*times
    
    time = start_time
    while time < end_time:
        timetable.append(tuple([time, time + interval]))
        time += interval
    
    return timetable

In my case, I’ll just execute the following in the Python interpreter:

start_time = Time(8, 0)
end_time = Time(18, 15)
for start, end in generate_timetable(start_time, end_time=end_time):
    print '%s-%s' % (start, end)

This prints the time slots that should go into each row of the spreadsheet, so I’ll just have to copy that, and I’ll be able to start filling my timetable!

Advertisements

Evan Miller: Rank Hotness With Newton’s Law of Cooling

Evan Miller: Rank Hotness With Newton’s Law of Cooling

Evan Miller: How Not To Sort By Average Rating

Evan Miller: How Not To Sort By Average Rating

Via Jeff Croft’s blog.

Lea Verou: In defense of reinventing wheels

Lea Verou: In defense of reinventing wheels

Pretty interesting article on reinventing wheels and DRY (well, not quite “yourself”). Found via Jeff Croft’s blog.

H2O templates not working with functions (method calls)

I love Django. And I love its template engine. It’s simply awesome, and template inheritance is such a powerful tool!

So, when I started developing in PHP with CodeIgniter, I hated its views and templates from the very beginning. Every second I dreamt of using Django views… And then I thought “Then I’ll code such an engine for PHP mysel!”

But, hey, are you mad? What are you thinking about? I don’t have time for that, and I don’t want to spend my time doing that, either. The first thing you should do is look up on the internet if there’s something like that!

And I found H2O (among others) and started using it. And happiness came to my life! …Until I realized I couldn’t evaluate method calls in the templates (although the syntax supports that).

So, if a class Field has a method widget_html(), and an instance variable field of class Field is visible from the template, this wouldn’t work:

{{ field.widget_html|safe }}

But why? If that’s supported by the syntax! After digging a bit on the internet (well, I didn’t have to dig that much; it was the second result), I found the solution on Stack Overflow. The issue is due to H2O needing to be told what methods are safe to be called from the templating language, and the way to do that is add an attribute to you class (in this case Field) where you specify the method names that are suitable for being called from the templates:

class Field {
    var $h2o_safe = array('label_html', 'widget_html');
    // ...
}

where we’re telling H2O that both methods label_html and widget_html of class Field are safe to be called.

Issue with dropdown login form in Bootstrap navbar and tooltip plugin

I’m designing a web site using Bootstrap and I wanted to add a navbar to the top of the page which shows a link to log in and a link to sign up. The sign up link would just lead you to the sign up page, but the log in link should actually reveal a dropdown menu with a log in form, so that you don’t have to load another page to log in.

I tried to write that log in form myself, but it didn’t look pretty well, so I decided to look up some recipe on the internet, and I found this: Adding a Drop Down Login Form to Bootstrap’s Navbar.

After adapting it a bit, I got this piece of code for the navbar and the log in form:

<ul class="nav pull-right">
    <li><a href="#">Profile</a></li>
    <li><a href="#">Settings</a></li>
    
    <li class="divider-vertical"></li>
    
    <li class="dropdown">
        <a href="#" class="dropdown-toggle" data-toggle="dropdown">
            Log in
            <b class="caret"></b>
        </a>
        <div class="dropdown-menu">
            <form action="/user/login" method="post" accept-charset="UTF-8">
                <input id="username" class="text" type="text" name="username"
                    size="30" placeholder="User name" />
                
                <input id="password" class="text" type="password" name="password"
                    size="30" placeholder="Password" />
                
                <input id="remember_me" class="remember" type="checkbox"
                    name="remember_me" value="1" />
                
                <label class="string optional"
                    for="remember_me"> Keep my session open
                    <i id="login_form_help" class="icon-question-sign" rel="tooltip" title="If you mark this option, we'll keep your session open, so that you won't have to enter your login data again in 30 days. After 30 days without entering the website, you'll have to login again."></i>
                    </label>
                
                <input class="btn btn-primary" type="submit" name="submit"
                    value="Log in" />
            </form>
        </div>
    </li>
    <li>
        <a href="/user/signup">Sign up</a>
    </li>
</ul>

However, I wanted to add a beautiful tooltip to the checkbox, so that it provides useful help for the inexperienced internet users. I decided to use Bootstrap’s tooltip plugin, and place a beautiful Bootstrap question mark icon, but I suddenly ran into an issue: when you hover the question mark with your mouse, the tooltip will be displayed, but beneath the dropdown menu, not on the top of it.

There’s been some discussion on this very same placement issue with tooltips, but only related to modals, not to dropdown menus:

And, thus, there is this simple and clean solution for that issue with tooltips and modals.

But, again, there’s no solution for dropdowns. So here it comes the solution for tooltips and dropdowns:

/*
Fix issue when showing tooltip in navbar login form: tooltip appears underneath the login form
Inspired by https://github.com/twitter/bootstrap/commit/12d3c2fe74bbe2570e47a2c8d7154a3011bd0770
*/

.dropdown-menu {
    z-index: 2050;
}

.tooltip {
    z-index: 2070;
}

Notice that using the .dropdown-menu .tooltip selector for the second CSS rule will actually not work, because the tooltip is added as a child of the body tag, not as a child of the navbar or the dropdown.

PS: I ran into this issue when using Safari to view the page, but I wasn’t trying any other browser at that time. However, when I tried on jsFiddle without the two CSS rules above, the issue didn’t appear anymore. Weird!