0

I'm creating a Django app, where users can create accounts for them and then add transactions to each account. Its part of a site that will allow users to track investment gains. I'm making use of the generic class views to achieve this.

Users add transactions to a specific account (code not shown) and then they must be able to view the transactions for that specific account using site/transactions/2/, where 2 is the account ID.

The problem is any logged in user can just change the url to get the transactions of another user's account. I can easily fix this by checking that the given account id belongs to the user, but I think there must be a better way. Are there better ways how I can achieve this? Should I perhaps not URL encode this? I'm not that fond of the idea of users seeing their account ids in the DB anyways.

Also, at a later stage I want to make it possible for the users to view some of their accounts' transaction in a single list. For instance, show all transactions for 3 of their 5 accounts. Then this URL method also won't really work. What other options do I have here?

In models.py I have:

class Account(models.Model):
    name = models.CharField(max_length=40, unique=True, db_index=True)
    user = models.ForeignKey(User)

class Transaction(models.Model):
    value = models.DecimalField(max_digits=15, decimal_places=2)
    date = models.DateField('transaction date')
    account = models.ForeignKey(Account)

In urls.py:

url(r'^(?P<account_id>\d+)/$', views.IndexView.as_view(), name='index'),  

In views.py:

class IndexView(LoginRequiredMixin, generic.ListView):
    model = Transaction

    def get_queryset(self):
        account_id = self.kwargs['account_id']
        queryset = Transaction.objects.filter(account_id=account_id)
        return queryset

And then I have a transaction_list template

Thanks

Kritz
  • 7,099
  • 12
  • 43
  • 73

1 Answers1

1

You could use the helper function get_object_or_404() to first get the account object if you want to have a 404 error.

Like this:

def get_queryset(self):
    account_id = self.kwargs['account_id']

    # raise 404 if no account is found for the current user
    account = get_object_or_404(Account, pk=account_id, user=self.request.user)

    queryset = Transaction.objects.filter(account=account)
    return queryset

For your second thing you mentioned, you could either make a new view, or just check if 'account_id' was in the url, and reuse your current view. You will need a new url either way.

urls.py:

url(r'^(?P<account_id>\d+)/$', views.IndexView.as_view(), name='index'),
url(r'^$', views.IndexView.as_view(), name='index'), 

modify your get_queryset() again for the case where no account id is in the url:

def get_queryset(self):
    # account_id will be None if the second url was the one that matched.
    account_id = self.kwargs.get('account_id', None) 

    if account_id:
        # raise 404 if no account is found for the current user
        account = get_object_or_404(Account, pk=account_id, user=self.request.user)

        queryset = Transaction.objects.filter(account=account)
    else:
        # we're going to show all transactions for the user, rather than a specific account
        queryset = Transaction.objects.filter(account__user=self.request.user)

    return queryset
jproffitt
  • 6,225
  • 30
  • 42
  • Thanks so much! I think that should do it. I'm quite new to django, so I'm wondering, is it good practice to have database field ids in the URL like this example? – Kritz Oct 10 '14 at 16:16
  • Its pretty standard, yeah. A better, friendlier way may be to use slugs instead. But I don't think its bad practice to use the id. – jproffitt Oct 10 '14 at 16:17