1

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!

8
  • 1
    what about customize the queryset by using a filter instead of .all() ?
    – PRMoureu
    Commented Aug 18, 2019 at 19:21
  • @PRMoureu thanks for the comment. Sure, I could add a company feature to the markets (as per option 1 above) and then filter the queryset by company. But would that filter be sensitive to the company affiliation of the current user (so users from company X only see markets for company X, and so forth)?
    – kh_one
    Commented Aug 18, 2019 at 19:28
  • 3
    If you have a relation between User <> Company and Market <> Company , you can filter the related markets: self.request.user gives the current user, from there you can select the company for a filter. Can you add the models if you need more details ?
    – PRMoureu
    Commented Aug 18, 2019 at 19:39
  • @PRMoureu this sounds promising! I've added the models to the original post.
    – kh_one
    Commented Aug 18, 2019 at 19:45
  • Ok, silly question but i'm not sure how you want to implement the relationships with Markets, one market can have many Companies ? And one user is tied to only one company right ?
    – PRMoureu
    Commented Aug 18, 2019 at 20:06

1 Answer 1

1

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.

2
  • Thanks, Daniel, this should work! One question: am I right in thinking that, since there are existing records in the database, doing this will require removing/altering existing migration files, given that ForeignKey is non-NULLable by default? Or can I get around that by setting null=True? Am asking because a version of this is already deployed on Heroku so want to minimise disruption to existing records.
    – kh_one
    Commented Aug 19, 2019 at 18:52
  • 1
    Yes, null=True will allow you to migrate Commented Aug 19, 2019 at 19:39

Not the answer you're looking for? Browse other questions tagged or ask your own question.