Django-Guardian: A Full Access Control Logic (ACL) Example

This image has absolutely nothing to do with django except that the word guardian reminds of an angel and angels remind me of my old magic the gathering days.

Django-Guardian is an object level permission library used in Django. I’ve recently implemented in place of my own home rolled solution. Boy, was I glad I did it. This blog post will take you through the entire process. If you have any good ideas on how to improve it, do leave a comment!

So the product that will be using this library an Academic Event Management Software. It is a SaaS site that allows academics to setup events. The role requirements are such:

  1. Each newly setup event will have a set of pre-defined roles
  2. Each role will have differing permissions
  3. It is possible for one user to have multiple roles for multiple events at the same time

For simplicity sakes, I will be reducing the number of roles I will be implementing to just 2 and they are a Delegate role and a Admin role.

Step 1: Event Creation

The save method of each newly created event instance calls a role_init method that creates the groups and permissions objects for each event.

Event Model

class Event(models.Model):
    name = models.CharField('Event Name', max_length=255)
    slug = models.SlugField(blank=True)

    class Meta:
        permissions = (
                ('change_registration', 'Change Registration Setup'),
                ('change_submission', 'Change Submission Setup'),
                ('view_registration_admin', 'View Registration Admin'),
                ('add_users', 'Add New Users'),
                )

    def save(self, *args, **kwargs):
        from role.utils import roles_init_new

        is_create = False
        if not self.id:
            is_create = True

        if not self.slug:
            self.slug = unique_slugify(self, self.name)

        super(Event, self).save(*args, **kwargs)

        if is_create:
            roles_init_new(self)

GroupEvent Model

class GroupEvent(models.Model):
    '''
    This model associates a group with an Event.
    Allows for easy retrieval of Group (Role Name) per event
    '''
    group = models.ForeignKey(Group)
    event = models.ForeignKey(Event)
    modified = models.DateTimeField(auto_now=True)
    created = models.DateTimeField(auto_now_add=True)

    def __unicode__(self):
        return u"%s" % self.group.name

    class Meta:
        unique_together = ['group', 'event']

roles_init_new function

def roles_init_new(event):
    '''
    Create new groups for the event
    '''
    g1 = Group.objects.get_or_create(name='%s: Admin - All' % event.name)[0]
    g2 = Group.objects.get_or_create(name='%s: Delegate' % event.name)[0]

    # ADMIN ALL GROUPS #
    assign('change_event', g1, event)  # change event details
    assign('change_registration', g1, event)  # change submission settings
    assign('change_submission', g1, event)  # change registration setup
    assign('view_registration_admin', g1, event)  # change registration tools
    assign('add_users', g1, event)  # user create functionality

    GroupEvent.objects.get_or_create(group=g1, event=event)
    GroupEvent.objects.get_or_create(group=g2, event=event)
    return True

The following things happen with the creation of a new event:

  1. 2 Group objects are created using the event name as part of the name of the Group. So an Event called Naruto would have the following groups:
    1. Naruto: Admin – All
    2. Naruto: Delegate
  2. Each group is that assigned a set of permissions. These permissions are defined in the Event model meta Class permissions. I am using a shortcut provided by django-guardian to assign permissions object level permissions to these groups. In my case, I am only assigning them to the Naruto: Admin – All group as the Naruto: Delegate group needs no permissions for now
  3. I created a intermediary table called GroupEvent which is essentially a model containing a FK to my Event model and a FK to the Group model. This is to allow for easy querying of results
  4. Django-guardian creates permissions associated with each group and object, which in this case is the newly created event, in the GroupObjectPermissions table. This is the model where the magic happens.

Step 2: User Creation & Association

This part is easy. Once you’ve set up the groups in step 1, all that’s left is a simple matter of associating a user to a desired group. Recall that each Group has a set of object level permissions associated with it and assigning a user into that Group will give the user all of those permissions as well. In code speak, it will look like this

event = Event.objects.get(name='Naruto')
user = User.objects.get(email=new_user@email.com)
group = Group.objects.get(name='%s: Admin - All' % event.name)
user.groups.add(group)

Step 3: Usage

So now that you have a user object that has been added to a group that has been assigned permissions and associated with an event , you’re ready to use it!

I have in my context processor, a method that retrieves all of the roles for the user regardless of Event

def user_roles(request):
    '''
    Return a list of all the user roles
    '''
    try:
        g = Group.objects.filter(user=request.user)
        ge_list = []

        for group in g:
            if GroupEvent.objects\
                    .filter(group=group)\
                    .exists():
                ge = GroupEvent.objects.get(group=group)
                ge_list.append(ge)
    except:
        ge_list = []

    return {'USER_ROLES': ge_list}

In my template, I can check for a user’s permissions like this and vary what I want to show:

{% load guardian_tags %}

{% get_obj_perms request.user for request.session.event as 'perms' %}

{% if 'change_event' in perms %}

show something

{% else %}

dont show something

{% endif %}

I can also protect my views by adding a decorator like so

@login_required
@permission_required_or_403('change_event', (Event, 'slug', 'event_slug'))
def event_admin_dashboard(request, event_slug):
    '''
    Admin dashboard for a particular Event
    '''
    event = get_object_or_404(Event, slug=event_slug)
    template = 'events/event_admin_dashboard.html'
    template_vars = {'event': event}
    return render(request, template, template_vars)

To get all the Groups a user is part of for a particular event, you can use a method like this

def get_roles_for_event(user, event):
    '''
    Returns a list of Group Event objects that the user
    is a group of
    '''
    group_events = GroupEvent.objects.filter(event=event)
    groups = Group.objects.filter(user=user)
    roles = []
    for group_event in group_events:
        for group in groups:
            if group == group_event.group:
                roles.append(group_event)
    return roles

Lastly, in my settings.py file, I have the following options:

ANONYMOUS_USER_ID = -1
GUARDIAN_RENDER_403 = True

I also have a template called 403.html in my main templates folder which will be shown if a user tries to access a page he doesn’t have permissions to.

Advertisements

DatabaseError: current transaction is aborted, commands ignored until end of transaction block

If you use Django with PostgresSQL, you might have encountered such an error before. In my experience, it is due to the following:

    1. You were messing around in shell and tripped it. To resolve this, see this answer: http://stackoverflow.com/questions/7753016/djangopostgres-current-transaction-is-aborted-commands-ignored-until-end-of
    2. You made changes to your models which you’ve forgotten to run ./manage.py schemamigrate <app_name> –auto and ./manage.py migrate <app_name>. Conversely, you could try ./manage.py syncdb
    3. Failing which, you can look in the logs of your postgres. For me the path is /usr/local/var/postgres/server.log. Look for the offending SQL entry and delete that record from your test database (it is a test database right?)

Growth Hacking: What is it?

Andrew Chen defines it as

Growth hackers are a hybrid of marketer and coder, one who looks at the traditional question of “How do I get customers for my product?”

Which I agree but I don’t agree completely on the next part:

and answers with A/B tests, landing pages, viral factor, email deliverability, and Open Graph

I would define a growth hacker as someone who writes code to scale the user acquisition process. If you were to slap a KPI to such a person it would look something like: acquired traffic/lines of code written.

Andrew gives the example of Airbnb’s tight integration with Craigslist. That’s definitely an example of growth hacking at work. Tight integration/import & export type functionality is really a marketing play that sits in the domain of the developer. No traditional marketer would be able to implement that. I will show you another impressive example of growth hacking at play:

Polyvore + Widgets

Polyvore is a beautiful collage making fashion site that’s brimming with creative users. Early on, they correctly identified that their users were spending so much time making a set, they were often immensely proud of their creation. So what Polyvore did was to allow their users to embed a set on their blog for them to show off. This brings traffic in 2 ways:

  1. Direct click throughs from the blog where the set is embedded on
  2. Keyword rich backlink acquisition through embedded keywords resulting in higher ranking pages in SERPS

I remember reading an article about Youtube a long time ago on how they purposely kept the urls short like this: http://www.youtube.com/watch?v=XYD6ZL0u7Ls

The rationale behind that is to prevent the link from being truncated in emails to maximise clickthrough rates from emails. That’s another example of growth hacking.

Obviously, the context has to be right for the implementation to work. There are also thin/dodgy implementations like address book/friend inviter spamming which works…. if this was 2005. Don’t do it. You’ll piss off your users and  your conversion rate would likely be low. Authenticity and trust is the currency of the internet, don’t blow it all in one bad move!

So, to reiterate once more, the job of the growth hacker is to get traffic to the website – a pure user acquisition oriented focus. Once the visitor is on the website, it’s a whole different ball game and ideally, another member of takes over.

The optimisation of the ‘click to $’ funnel to me is a sales process where for a website, the sales person is the website! So tools like A/B testing, analytics, conversion funnels, , engagement loops, retention and cohort analysis etc comes into play. There is no ‘growth’ to ‘hack’ here. But it doesn’t mean that once the user is on your site, there isn’t any growth hacks to be had! See viral loops.

Of course, all the optimisations would be for nought if your growth hacker brings in unqualified traffic of course or if you haven’t found product/market fit yet. So the right hand gotta know what the left is playing at otherwise there won’t be any hands left to play.

Obviously, a good VP of Marketing, as suggested by Andrew Chen, ought to know all of these processes and have a good overview of the the entire start to finish funnel but it is not the prerogative of the growth hacker. I like to keep my job scopes very well defined.

Your thoughts?

Hacking a Social Media Presence 101

I wrote this for my bro a while ago when he was figuring how to get more visibility online. Thought it might be useful to repost this to my blog. This is more applicable for an individual as opposed to a company.

Define Objectives

You have to figure out what you want to achieve online. It could be any of the following:

  1. Increase sales through marketing
  2. Create dialogue with customers
  3. Increase brand/person exposure and awareness
  4. Reach out to like minded people for collaboration
  5. Could be all of the above!

Who’s the target audience you’re trying to reach

  1. Create a user archetype. This is an assumption you need to verify later on (e.g., 20 – 30 something marketing professionals). Have it in your mind, bonus points if you write it down and stick it on the wall. I find the act of writing something helps to clarify my thoughts.
  2. Use blog aggregators like Technorati to search for bloggers talking about your area of interest. Bookmark them, set up an RSS feed and start joining the conversation. Ask yourself: Does the archetype still hold? Are there more than 1 archetypes?
  3. What topics are interesting to them? What can you say or write about that is valuable to them?
  4. Look for relevant Twitter hashtags and tweets.
  5. Does a subreddit exist for your area of interest?

Keyword Research

  1. What keywords are relevant? Another way of looking at this question is: what should your users type into Google such that your blog/content shows up?
    1. Make a top 5 list
      1. Be specific. So don’t use terms like ‘make money’ but ‘make money from running a small office home office selling headphones’
    2. Set up Google alerts for these keywords http://www.google.com/alerts
      1. This helps you establish the sites/people who so that you can follow them
    3. Use Google Adwords Keyword Tool to see relevant keywords https://adwords.google.co.uk/select/KeywordToolExternal
      1. There are tons of keyword research tools out there but I find Google KW tools to be the best
  2. Who are currently ranking for these keywords on SERPS? If possible engage in dialogue with them through leaving comments on their blogs
    1. Note that you will get different results depending on which search engine you use (Bing, Yahoo) and also the country you are accessing the information from (www.google.com vs http://www.google.co.uk)
  3. Use Google Insights and Google autocomplete/autosuggest for additional datapoints of interest
  4. Are people using the same terms on other social media sites like Twitter?

Channels

  1. Blog
    1. Services such as WordPress/Blogger. This is super important and the most basic and low hanging fruit you can do to get things going.
    2. It’s probably tempting to get a blog on tumblr but I recall having a hard time integrating a comment plugin like disqus into it. So go with wordpress if you can help it (unless your target audience consists of young/creative/hipster people which tumblr seems to be full of)
  2. Twitter
    1. Search and follow the people you have identified in Step 3 and other relevant personalities you know of. http://wefollow.com/ is a helpful site to find such people
  3. LinkedIn
    1. Update profile to include keywords identified in Step 3
  4. Quora
    1. Follow topics and answer questions in your area of interest
  5. Slideshare
    1. Upload presentations and include links to your blog, Twitter, Linkedin accounts
  6. Industry specific channels
    1. Every industry has its own eco system of websites and such. Participating in the conversations occurring on those sites are important
    2. E.g., Flickr for photography enthusiasts, Vimeo for video buffs, Mendeley for academics

Tactics

  1. Consistency is key. This is a long term investment so don’t be discouraged if you don’t see results immediately. Try to push out a blog post at least once a week. Everyday if you can help it
  2. Description, profile image and contact details of yourself should be consistent on all channels
  3. Prepare a short 2 liner description about who you are and what you do. Throw this in your Twitter profile, Facebook about me, LinkedIn bio etc you get the picture
  4. From the keywords you have identified above, use them consistently in writing/uploading content on your social media channels. Also remember who you are writing for as identified in Step 2.

Secret Sauce

The currency of the internet these days is authenticity. Don’t fake it!!. If you have good content and you’re a domain expert in your respective field, it’s as simple as putting your thoughts on a blog and having a conversation with like minded people. In today’s noisy internet, good quality information is hard to come by so if you have something to share that people will find valuable, they will find a way to your content! This takes time to build up so again, persist!