3

In my Django site, I have several apps, each app is meant to be used by a specific group of users only. Let's say I have 2 groups of users: A and B. I have two apps: App1 whose URL starts with /app1, and App2 whose URL starts with /app2. I want to assign permission to use App1 to group A, and App2 to group B. Users from B can never access all URLs starting with /app1 and vice versa.

How can I do it?

tnq177
  • 583
  • 1
  • 8
  • 19
  • 1
    Use the [`user_passes_test`](https://docs.djangoproject.com/en/1.5/topics/auth/default/#django.contrib.auth.decorators.user_passes_test) decorator. – Burhan Khalid Jul 01 '14 at 08:08
  • possible duplicate of [How to write a custom decorator in django?](http://stackoverflow.com/questions/5469159/how-to-write-a-custom-decorator-in-django) – sundar nataraj Jul 01 '14 at 08:10

2 Answers2

7

Given this answer,

Here is the code for adding all existing permissions of an app under a Group. For exemple if you have App1 and App2, and you want to automatically create 2 groups named app1 and app2 containing permissions to the models of each app (respectively), try this :

For App1 on apps.py :

from django.apps import AppConfig
from django.db.models.signals import post_migrate

class App1Config(AppConfig):
    name = 'app1'

    def ready(self):
        from .signals import populate_models
        post_migrate.connect(populate_models, sender=self)

Create a signals.py file that contains :

def populate_models(sender, **kwargs):
    from django.apps import apps
    from .apps import App1Config
    from django.contrib.auth.models import Group, Permission
    from django.contrib.contenttypes.models import ContentType

    group_app, created = Group.objects.get_or_create(name=App1Config.name)

    models = apps.all_models[App1Config.name]
    for model in models:
        content_type = ContentType.objects.get(
            app_label=App1Config.name,
            model=model
        )
        permissions = Permission.objects.filter(content_type=content_type)
        group_app.permissions.add(*permissions)

Do the same for App2

Then assign users to their groups.

For usage :

Create a file called permissions.py on each app and add :

from .apps import App1Config

def is_in_group_app1(user):
    return user.groups.filter(name=App1Config.name).exists() 

In views.py use :

from django.contrib.auth.decorators import login_required, user_passes_test
from .permissions import is_in_group_app1

@login_required(login_url='where_to_redirect')
@user_passes_test(is_in_group_app1) 
def myview(request):
    # Do your processing

For CBV :

@method_decorator(user_passes_test(is_in_group_app1), name='dispatch')
class LogListView(ListView):
    """ Displays all logs saved on App1 """
    model= Logger.objects.order_by('-date_created')

Create a folder named templatestag and subfolder app1 and a has_perms.py file :

from django import template
from app1.permissions import is_in_group_app1
register = template.Library()

@register.filter
def has_perms(user):
    return is_in_group_app1(user)

In your template :

{% load has_perms %}

{% if request.user|has_perms %}
    <li class="nav-item">
        <a href="{% url 'app1:log' %}" class="nav-link">
            <i class="icon-history"></i>
            <span data-i18n="nav.dash.main">App1 Log</span>
        </a>
    </li>
{% endif %}

It took me a while to find all this process, so if it can help others :

Enjoy ;) !

HamzDiou
  • 588
  • 9
  • 15
5

You could do this, but I would recommend against it.

You should be using the user_passes_test() or the permission_required() decorators on the views that implement App1 and App2.

This would be achieved by adding something like this:

def in_group_a(user):
    return user.groups.filter(name="Group A").exists()

def in_group_b(user):
    return user.groups.filter(name="Group B").exists()

@user_passes_test(in_group_a)
def app1_view(request):
    ...

@user_passes_test(in_group_b)
def app2_view(request):
    ...

In order to acheive this as asked (with a single check based on the url prefix) you would have to have a single view per application, that was accessed via a url pattern like:

url(r'^app1/(?P<remaining_url>.*)$', 'app1.views.app1`)

Your view would then have to run user_passes_test() as above, and manually parse the remaining_url parameter to work out what to do next:

@user_passes_test(in_group_a)
def app1(request, remaining_url):
    # parse remaining_url and work out what to do

But that parsing and dispatching operation is exactly what the urlconf in Django is for.

In theory, you could create another app-specific urlconf (not referenced from your main urls.py) and manually use its API to re-dispatch remaining_url to a set of views.

Jamie Cockburn
  • 7,379
  • 1
  • 24
  • 37