Before you can estimate user stories, you need to get a group of your team mates together in a group. Here’s how the Anonymous Agile Estimator does that.

It’s been difficult to carve out time for this project with any consistency and there are a number of problems with test coverage that I have not looked at yet.

This post, and the code behind it, is an effort to make progress towards a working prototype of the application. A user can now invite others to join the group, see the group page and users can also leave a group.

The pages still look a little crude and the test code coverage is slipping further behind. I’ll circle back around to resolve those issues later.

I learned a few new things about Flask and SQLAlchemy and have documented them below. But first here is what I’ve added to the application this week.

View a Group

After creating a group, rather than just show it in a list, the user will be redirected to the group page. The funcitonality to add an issue for estimation is not there yet; the page just shows the other users that are in the group and provides a link that the owner can send on.

The page looks a little different for users that don’t own the group. They don’t need the ability to invite users to the group.

Join a Group

There is no protection, at the moment, to stop you joining a group that you have not been invited to. All groups are open to everyone. The URL is simply group\<id>\join. A confirmation page shows the name of the group before submitting a POST to join the group. The user will be redirected to the group page after joining.

Join Us

Leave a Group

It might be useful to leave a group. Maybe you cannot take part in the entire estimation session and to avoid delaying the rest of your team mates, you can leave the group so that the system will not be waiting for your estimate.

It is possible that the meeting facilitator might not take part in the estimation. Perhaps they are filling in for the regular scrum master and do not know the team’s usual story sizes.

The owner of the group still needs to be able to see the group, invite other users and add issues.

There is a confirmation page that will allow the user to change their mind before leaving the group.

Learnings

I learned a few new things related to Flask and SQLAlchemy that I think are worth noting as they may trip up other beginners.

Flushing

A new table has been added to the database to record which users are part of which groups. It is called membership and has a primary key and foreign keys to the estimation_group and user tables. Here is the code:

class Membership(db.Model):
	id = db.Column(db.Integer, primary_key=True)
	group_id = db.Column(db.Integer, db.ForeignKey('estimation_group.id'), nullable=False)
	user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)

	def __init__(self, group, user):
		self.group_id = group.id
		self.user_id = user.id

When creating a group, I want to add the creator into the group as a member. To do this I need to have its primary key field populated. This will not be done until the object is written into the table.

By default this will only happen when the session is committed, but that is too late for me, as I want to populate the membership row in the same commit. What’s needed is to flush the creation of the group row to the db with db.session.flush().

This is useful as you may want both the rows to be committed together, or if there is a problem, to roll back together. Inserting and committing just one of the rows may create an inconsistent state.

group = Group(group_name, user)
db.session.add(group)
db.session.flush()
membership = Membership(group, user)
db.session.add(membership)
...
db.session.commit()

Joining Tables

Now that there is a another table in the schema, querying the database has become slightly more complicated. We will need to query what groups a user is in. We want to return the groups based on a query of the membership table for a given user id.

other_groups = Group.query.join(Membership).filter(Membership.user_id==user_id).all()

Similarly, to show the members of a group on screen, we would need to query all the users that belong to the group:

members = User.query.join(Membership).filter(Membership.group_id==id).all()

Variables in Path

In an earlier post (here’s the link), we used the url_for function to build URLs for use with redirection. What happens when we need to include variables in the path?

This method takes additional named paramaters that it uses to substitute for the URL variables. The function in the controller that returns the estimation group is defined as:

@web.route('/group/<int:id>', methods=['GET'])
def view_group(id):

After creating the group, we can redirect to the view group page using:

return redirect(url_for('web.view_group', id=id))

As the group creator, we need to be able to invite other users to join the group. Since I don’t want to collect emails in this application, it is easier to send a link to the users.

By default, url_for will create a relative link, but this will not be of much use for someone that doesn’t know where the application is hosted.

We need to use the _external parameter so that the full URL is returned to the user. Creating the join group link can be done with:

join_link = url_for("web.join_group", id=id, _external=True)

That wraps it up for this week. Next time I will add the ability to create issues for a group.

Let me know if you have any questions or comments at [email protected].