Shaun
Shaun

Reputation: 69

Restrict `UpdateView` dataset for authenticated user in Class Based Views

I have a Django project where I extended the User to have a Profile using a OneToOneField. I'm using CBV UpdateView which allows users to update their profile. The URL they visit for this is ../profile/user/update. The issue I have is that if a user types in another users name they can edit the other persons profile. How can I restrict the UpdateView so the authenticated user can only update their profile. I was trying to do something to make sure user.get_username == profile.user but having no luck.

Models.py

from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.core.urlresolvers import reverse

class Profile(models.Model):
    # This field is required.
    SYSTEM_CHOICES = (
        ('Xbox', 'Xbox'),
        ('PS4', 'PS4'),
    )
    system = models.CharField(max_length=5,
                                    choices=SYSTEM_CHOICES,
                                      default='Xbox')
    user = models.OneToOneField(User)
    slug = models.SlugField(max_length=50)
    gamertag = models.CharField("Gamertag", max_length=50, blank=True)
    f_name = models.CharField("First Name", max_length=50, blank=True)
    l_name = models.CharField("Last Name", max_length=50, blank=True)
    twitter = models.CharField("Twitter Handle", max_length=50, blank=True)
    video = models.CharField("YouTube URL", max_length=50, default='JhBAc6DYiys', help_text="Only the extension!", blank=True)
    mugshot = models.ImageField(upload_to='mugshot', blank=True)

    def __unicode__(self):
            return u'%s' % (self.user)

    def create_user_profile(sender, instance, created, **kwargs):
        if created:
            Profile.objects.create(user=instance, slug=instance)

    post_save.connect(create_user_profile, sender=User)

    def get_absolute_url(self):
        return reverse('profile-detail', kwargs={'slug': self.slug})

Views.py

from django.shortcuts import render
from django.views.generic import DetailView
from django.views.generic.edit import UpdateView
from django.views.generic.list import ListView

from profiles.models import Profile


class ProfileDetail(DetailView):

    model = Profile

    def get_context_data(self, **kwargs):
        context = super(ProfileDetail, self).get_context_data(**kwargs)
        return context

class ProfileList(ListView):
    model = Profile
    queryset = Profile.objects.all()[:3]

    def get_context_data(self, **kwargs):
        context = super(ProfileList, self).get_context_data(**kwargs)
        return context

class ProfileUpdate(UpdateView):
    model = Profile
    fields = ['gamertag', 'system', 'f_name', 'l_name', 'twitter', 'video', 'mugshot']
    template_name_suffix = '_update'

    def get_context_data(self, **kwargs):
        context = super(ProfileUpdate, self).get_context_data(**kwargs)
        return context

Admin.py

from django.contrib import admin
from models import Profile

class ProfileAdmin(admin.ModelAdmin):
    prepopulated_fields = {'slug': ('user',), }

admin.site.register(Profile, ProfileAdmin)

Urls.py for Profiles app

from django.conf.urls import patterns, url
from django.contrib.auth.decorators import login_required
from profiles.views import ProfileDetail, ProfileUpdate

urlpatterns = patterns('',
    url(r'^(?P<slug>[-_\w]+)/$', login_required(ProfileDetail.as_view()), name='profile-detail'),
    url(r'^(?P<slug>[-_\w]+)/update/$', login_required(ProfileUpdate.as_view()), name='profile-update'),
)

Profile_update.html

{% extends "base.html" %} {% load bootstrap %}

{% block content %}

{% if user.is_authenticated %}

  <h1>Update your profile</h1>

  <div class="col-sm-4 col-sm-offset-4">
    <div class="alert alert-info alert-dismissible" role="alert">
      <button type="button" class="close" data-dismiss="alert" aria-label="Close">
        <span aria-hidden="true">&times;</span>
      </button>
      <strong>Heads up!</strong> Other users can find you easier if you have a completed profile.
    </div>
    <form enctype="multipart/form-data" method="post" action="">{% csrf_token %}
      {{ form|bootstrap }}
      <input class="btn btn-default" type="submit" value="Update" />
    </form>
  </div>


{% else %}
<h1>You can't update someone elses profile.</h1>
{% endif %}

{% endblock %}

Upvotes: 1

Views: 3245

Answers (3)

freezed
freezed

Reputation: 1339

To avoid access to data unrelated to the connected user when using Class Based View (CBV), you can use Dynamic filtering and define queryset instead on model attributes.

If you have a book.models with a ForeignKey (named user here) on auth.models.user you can easily restrict acces like this :

# views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import ListView
from books.models import Book

class BookList(LoginRequiredMixin, ListView):

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

See more explanation in the documentation about CBV - Viewing subsets of objects

Specifying model = Publisher is really just shorthand for saying queryset = Publisher.objects.all(). However, by using queryset to define a filtered list of objects you can be more specific about the objects that will be visible in the view.

[…]

Handily, the ListView has a get_queryset() method we can override. Previously, it has just been returning the value of the queryset attribute, but now we can add more logic. The key part to making this work is that when class-based views are called, various useful things are stored on self; as well as the request (self.request) this includes the positional (self.args) and name-based (self.kwargs) arguments captured according to the URLconf.

Upvotes: 3

Tomasz Majk
Tomasz Majk

Reputation: 19

  • your template.html:

{% if request.user.is_authenticated and profile.user == request.user %}
your form
{% else %}
u cannot edit that profile - its not yours...
{% endif %}

Upvotes: 1

mishbah
mishbah

Reputation: 5597

How about something like this:

from django.contrib.auth.views import redirect_to_login


class ProfileUpdate(UpdateView):
    [...]

    def user_passes_test(self, request):
        if request.user.is_authenticated():
            self.object = self.get_object()
            return self.object.user == request.user
        return False

    def dispatch(self, request, *args, **kwargs):
        if not self.user_passes_test(request):
            return redirect_to_login(request.get_full_path())
        return super(ProfileUpdate, self).dispatch(
            request, *args, **kwargs)

In this example, the user is redirected to default LOGIN_URL. But you can easily change it . to redirect user to their own profile.

Upvotes: 6

Related Questions