kh_one
kh_one

Reputation: 257

Object-level permissions in Django

I have a ListView as follows, enabling me to loop over two models (Market and ScenarioMarket) in a template:

class MarketListView(LoginRequiredMixin, ListView):
    context_object_name = 'market_list'
    template_name = 'market_list.html'
    queryset = Market.objects.all()
    login_url = 'login'

    def get_context_data(self, **kwargs):
        context = super(MarketListView, self).get_context_data(**kwargs)
        context['scenariomarkets'] = ScenarioMarket.objects.all()
        context['markets'] = self.queryset
        return context

The two market models are as follows:

class Market(models.Model):
    title = models.CharField(max_length=50, default="")
    current_price = models.DecimalField(max_digits=5, decimal_places=2, default=0.50)
    description = models.TextField(default="")
    shares_yes = models.IntegerField(default=0)
    shares_no = models.IntegerField(default=0)
    b = models.IntegerField(default=100)
    cost_function = models.IntegerField(default=0)
    open = models.BooleanField(default=True)

    def __str__(self):
        return self.title[:50]

    def get_absolute_url(self):
        return reverse('market_detail', args=[str(self.id)])

class ScenarioMarket(models.Model):
    title = models.CharField(max_length=50, default="")
    description = models.TextField(default="")
    b = models.IntegerField(default=100)
    cost_function = models.IntegerField(default=0)
    most_likely = models.CharField(max_length=50, default="Not defined")
    open = models.BooleanField(default=True)

    def __str__(self):
        return self.title[:50]

    def get_absolute_url(self):
        return reverse('scenario_market_detail', args=[str(self.id)])

And my user model is as follows:

class CustomUser(AbstractUser):
    points = models.DecimalField(
        max_digits=20, 
        decimal_places=2,
        default=Decimal('1000.00'),
        verbose_name='User points'
    )

    bets_placed = models.IntegerField(
        default=0,
        verbose_name='Bets placed'
    )

    net_gain = models.DecimalField(
        max_digits=20, 
        decimal_places=2,
        default=Decimal('0.00'),
        verbose_name='Net gain'
    )

    class Meta:
        ordering = ['-net_gain']

What I want happen is that different users see different sets of markets. For example, I want users from company X to only see markets pertaining to X, and same for company Y, Z, and so forth.

Four possibilities so far, and their problems:

  1. I could hardcode this: If each user has a company feature (in addition to username, etc.), I could add a company feature to each market as well, and then use if tags in the template to ensure that the right users see the right markets. Problem: Ideally I'd want to do this through the Admin app: whenever a new market is created there, it would be specified what company can see it.

  2. I could try to use Django's default permissions, which of course would be integrated with Admin. Problem: Setting a view permission (e.g., here) would concern the entire model, not particular instances of it.

  3. From googling around, it seems that something like django-guardian might be what I ultimately have to go with. Problem: As I'm using a CustomUser model, it seems I might run into problems there (see here).

  4. I came across this here on SO, which would enable me to do this without relying on django-guardian. Problem: I'm not clear on how to integrate that into the Admin app, in the manner that django-guardian seems able to.

If anyone has any advice, that would be greatly appreciated!

Upvotes: 1

Views: 353

Answers (1)

Daniel Roseman
Daniel Roseman

Reputation: 599956

You can add some relationships between the models:

class Company(models.Model):
    market = models.ForeignKey('Market', on_delete=models.CASCADE)
    ...

class CustomUser(AbstractUser):
    company = models.ForeignKey('Company', on_delete=models.CASCADE)
    ...

then in your view you can simply filter the queryset as appropriate:

class MarketListView(LoginRequiredMixin, ListView):
    context_object_name = 'market_list'
    template_name = 'market_list.html'
    login_url = 'login'

    def get_queryset(self):
        return Market.objects.filter(company__user=self.request.user)

Note, you don't need the context['markets'] = self.queryset line in your get_context_data; the queryset is already available as market_list, since that's what you set the context_object_name to.

Upvotes: 1

Related Questions