Protection against editing another user's profile in django - django

I have trouble with protection against editing another user's profile and I don't have any idea how to solve this issue.
Ofcourse I know that I can do sth like this:
<a class="nav-item nav-link" href="{% url 'profile_change' user.pk %}">
or something similar.
But I don't know what i can do in situation when user write him/herself webadress. I mean situation when user with pk = 2, would write adress: "website address/profile/change/1"
Here is my models, views and urls:
#urls.py
urlpatterns = [
path('profile/', views.profile, name='profile'),
path('profile/list/', views.ProfileListView.as_view(), name='profile_changelist'),
path('profile/add/', views.ProfileCreateView.as_view(), name='profile_add'),
path('profile/change/<int:pk>/', views.ProfileUpdateView.as_view(), name='profile_change')
]
#views.py
class ProfileCreateView(CreateView):
model = Profile
form_class = ProfileForm
success_url = reverse_lazy('profile_changelist')
def form_valid(self, form, **kwargs):
form.instance.user = self.request.user
return super().form_valid(form)
class ProfileUpdateView(UpdateView):
model = Profile
fields = ('first_name', 'last_name', 'year')
success_url = reverse_lazy('profile_changelist')
#login_required
def profile(request):
if Profile.objects.filter(user=request.user.id).count() == 1:
return render(request, 'profiles/profile.html')
else:
return HttpResponseRedirect('add')
#models.py
class Profile(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
first_name = models.CharField(max_length=100, null=True)
last_name = models.CharField(max_length=100, null=True)
year = models.IntegerField(choices=YEARS, default=1)

You must check what the user from request object is a editable user from parameters:
from django.http import Http404
class ProfileUpdateView(UpdateView):
model = Profile
fields = ('first_name', 'last_name', 'year')
success_url = reverse_lazy('profile_changelist')
def get(self, request, *args, **kwargs):
if not request.user.id == self.kwargs.get('pk'):
raise Http404
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
if not request.user.id == self.kwargs.get('pk'):
raise Http404
return super().post(request, *args, **kwargs)

Related

Cross queries in django

I have two models as below
class Watched(Stamping):
user = models.ForeignKey("User", null=True, blank=True, on_delete=models.CASCADE,
default=None)
count = models.PositiveIntegerField()
class Link(Stamping):
...
user = models.ForeignKey(User, on_delete=models.CASCADE, default=None)
url = models.CharField(max_length=256, default=None)
watched = models.ForeignKey(Watched, null=True, blank=True, on_delete=models.CASCADE, default=None)
...
My forms.py
class SimpleLink(forms.Form):
url = forms.URLField(max_length=256)
A user can create a Link object and when some conditions are met, the object will be added to Watched. The Watched model contains objects created by different users.
Now I want to filter the Watched class and grab only the objects created by the requesting user in the Link model but I don't know how I can achieve that. Any help will be appreciated.
A sample of what I want to achieve is...
Watched.objects.filter(Link.objects.filter(user=request.user). I know my sample is crazy. But from the outside query, I want to grab the Link objects created by user making the request
You need to limit the queryset in your ModelForm. A ModelForm will thus look like:
from django import forms
class LinkForm(forms.ModelForm):
def __init__(self, *args, user=None, **kwargs):
super().__init__(*args, **kwargs)
if user is not None:
self.fields['watched'].queryset = Watched.objects.filter(
link__user=user
)
class Meta:
model = Link
fields = ['url', 'watched']
In our view, we can then set the user object:
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect, render
#login_required
def some_view(request):
if request.method == 'POST':
form = LinkForm(request.POST, user=request.user)
if form.is_valid():
form.instance.user = request.user
form.save()
return redirect('name-of-some-form')
else:
form = LinkForm(user=request.user)
return render(request, 'some-template.html', {'form': form})
For a class-based view, we can override the .get_form_kwargs(…) method [Djangod-doc]:
from django.contrib.auth.mixins import LoginRequiredMixin
class SomeView(LoginRequiredMixin, CreateView):
form_class = LinkForm
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)

UNIQUE constraint failed: auth_user.username while updating user info

I am trying to give a user the option to change his/her first/last name through a ModelForm. When I press submit, I get hit with the UNIQUE constraint failed: auth_user.username error. Here are my codes:
students/forms.py:
class EditProfileForm(UserChangeForm):
def clean_password(self):
# Overriding the default method because I dont want user to change
# password
pass
class Meta:
model = User
fields = (
'first_name',
'last_name',
)
students/views.py:
User = get_user_model()
def student_profile_view(request, slug):
if request.method == 'GET':
# forms
edit_name_form = EditProfileForm(instance=request.user)
context = {
'edit_name_form': edit_name_form,
}
return render(request, "students/profile.html", context)
class ChangeNameView(SuccessMessageMixin, UpdateView):
template_name = 'students/edit_profile.html'
model = User
form_class = EditProfileForm
success_message = "Your name has been updated"
def post(self, request, *args, **kwargs):
form = self.get_form()
if form.is_valid():
form.instance.student_profile = StudentProfile.objects.get(slug=request.user.student_profile.slug)
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
"""If the form is valid, save the associated model."""
form.instance.username = self.request.user
self.object = form.save(commit=False)
return super().form_valid(form)
def get_success_url(self):
return reverse('students:student_profile_view', kwargs={'slug': self.object.student_profile.slug})
also fyi, User model is foreign key with StudentProfile.
students/models.py:
class StudentProfile(models.Model):
user = models.OneToOneField(User, related_name='student_profile', on_delete=models.CASCADE)
slug = models.SlugField(blank=True, unique=True)
avatar = models.ImageField(upload_to='student_profile/', null=True, blank=True)
description = models.CharField(max_length=120, null=True, blank=True)
objects = models.Manager()
def __str__(self):
return self.user.username
def get_absolute_url(self):
return reverse("students:student_profile_view", kwargs={"slug": self.slug})
I am pretty new to class based view so maybe I'm doing something wrong there?
I assume you do not have the user within the form so you need the form
def get_context_data (self, *args, **kwargs)
ctx = super().get_context_data(*args, **kwargs)
if self.request.method == 'POST':
ctx['form'] = EditProfileForm(instance=self.request.user)
and remove def form_valid()

django authorization in class-based view

I'm trying to restrict access of CRUD pages to the owners, but I can't find the class-based view equivalent of "if request.user != post.author raise Http404". Thx for your time.
models.py
class Article(models.Model):
title = models.CharField(max_length=255)
body = models.TextField()
date = models.DateTimeField(auto_now_add=True)
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('article_detail', args=[str(self.id)])
views.py
class ArticleUpdateView(LoginRequiredMixin, UpdateView):
model = Article
fields = ['title', 'body']
template_name = 'article_edit.html'
login_url = 'login'
I tried the following (and many other combination arround those lines), but it isn't working.
def get(self, request, *args, **kwargs):
if self.request.user == self.obj.author:
raise Http404()
Youn can do something like this:-
class ArticleUpdateView(LoginRequiredMixin, UpdateView):
model = Article
fields = ['title', 'body']
template_name = 'article_edit.html'
login_url = 'login'
def get(self, request, *args, **kwargs):
self.obj = self.get_object()
if self.request.user != self.obj.author:
raise Http404()
return super(ArticleUpdateView, self).get(request, *args, **kwargs)
I think you can override the get_queryset method to achieve this. For example:
class ArticleUpdateView(...):
def get_queryset(self):
queryset = super(ArticleUpdateView, self).get_queryset()
return queryset.filter(author = self.request.user)
So, when a user tries to update an post which is not created by him, then he will not be able to get it because will not be able find the post object in Queryset provided by get_queryset method. For details, please SingleObjectMixin which is later sub-classed by UpdateView. FYI you don't need to override the get method for this implementation.

Django image upload issue

I'm trying to upload an image. This is an avatar image for the profile of the user.
Currently, the form return no error, but I have nothing written on my database or in my folder media/avatar/.
What's wrong ?
My view :
def view_avatar(request):
if request.method == 'POST':
form = UploadFileForm(request.POST, request.FILES, instance=request.user.profile)
if form.is_valid():
form.save()
else:
form = UploadFileForm(instance=request.user.profile)
return render(request, 'avatar.html', locals())
My form :
class UploadFileForm(forms.ModelForm):
class Meta:
model = Profile
fields = ('avatar',)
My model :
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
birthdate = models.DateField(null=True, blank=True)
avatar = models.ImageField(upload_to='media/avatar/', blank=True, null=True)
It's because the form which you are using is inherited from forms.Form , you need to use forms.ModelForm for saving the instance directly.
Change this line,
class UploadFileForm(forms.ModelForm):
class Meta:
model = Profile
fields = ('avatar', )
def save(self, *args, **kwargs):
profile, created = Profile.objects.get_or_create(user=self.user)
profile.avatar = self.cleaned_data['avatar']
profile.save()
return profile
Also, edit in your views like this,
if form.is_valid():
file = form.save(commit=False)
file.user = request.user
file.save()
For making a profile you can use signals.
This way whenever a new user been added, a profile will be generated for that user automatically
Your models.py:
from django.conf import settings
from django.db.models.signals import post_save
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL)
birthdate = models.DateField(null=True, blank=True)
avatar = models.ImageField(upload_to='media/avatar/%y/%m/%d', blank=True, null=True)
def post_save_profile(sender, instance, created, *args, **kwargs):
if created:
try:
Profile.objects.create(user=instance)
except:
pass
post_save.connect(post_save_profile, sender=settings.AUTH_USER_MODEL)
and for updating the information like birthday and avatar you can use ModelForm.
forms.py:
class UploadFileForm(forms.ModelForm):
class Meta:
model = Profile
fields = ('birthdate', 'avatar')
def __init__(self, *args, **kwargs):
super(UploadFileForm, self).__init__(*args, **kwargs)
self.fields['avatar'].required = False
your views.py:
def view_avatar(request):
user = request.user
if request.method == "POST":
form = UploadFileForm(request.POST, request.FILES, instance=user.profile)
if form.is_valid():
form.save()
for avatar in template you can use this:
<img src="{% if user.profile.avatar %}{{ user.profile.avatar.url }}{% else %}{% static 'no-avatar.jpg' %}{% endif %}"><i></i>
You can write custom Save method for this like this.
View:
def view_avatar(request):
if request.method == 'POST':
form = UploadFileForm(request.POST, request.FILES, user=request.user)
if form.is_valid():
form.save()
else:
form = UploadFileForm()
return render(request, 'avatar.html', locals())
Form:
class UploadFileForm(forms.Form):
class Meta:
model = Profile
fields = ('avatar', )
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super(UploadFileForm, self).__init__(*args, **kwargs)
self.fields['avatar'].required = False
avatar = forms.FileField()
def save(self, *args, **kwargs):
user_profile, created = Profile.objects.get_or_create(user=self.user)
user_profile.avatar = self.cleaned_data.get('avatar')
user_profile.save()
return user_profile
I forgot the enctype !
The solution was :
<form method="POST" action="" enctype="multipart/form-data">

How to pass currently logged in user to the model using generic views?

I am new to django and I was asked to associate currently logged in user with Context (my model class) the user creates.
How/where to retrieve the User (from request.user) object? How to set it in the model?
Is it possible to pass request.user to view using generic views?
Here is my Context class:
from django.contrib.auth.models import User
class Context(models.Model):
title = models.CharField(max_length=32)
description = models.TextField()
user = models.ForeignKey(User,null=False)
Here is the form:
class ContextForm(ModelForm):
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.add_input(Submit('submit', 'Submit'))
super(ContextForm, self).__init__(*args, **kwargs)
class Meta:
model = Context
exclude = ('user')
URL from urls.py:
url(r'^create/$', CreateView.as_view(model=Context, form_class=ContextForm),name='context_create'),
Finally, context_form.html with crispy form
{% load crispy_forms_tags %}
{% block content %}
{% crispy form %}
{% endblock %}
Thank you in advance for any help.
Check out django-braces! It has a form mixin that pops the current user from the kwargs. There's also a mixin for a class based view that passes the user to the form.
If you're doing class based views, just use the mixins provided in forms.py and views.py, and override the save() method.
forms.py:
from braces.forms import UserKwargModelFormMixin
class ContextForm(UserKwargModelFormMixin, ModelForm):
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.add_input(Submit('submit', 'Submit'))
super(ContextForm, self).__init__(*args, **kwargs)
class Meta:
model = Context
fields = ('title', 'description',)
def save(self, force_insert=False, force_update=False, commit=True):
obj = super(ContextForm, self).save(commit=False)
obj.user = self.user
if commit:
obj.save()
return obj
views.py
from braces.views import UserFormKwargsMixin
class CreateContextView(UserFormKwargsMixin, CreateView)
model = Context
form_class = ContextForm
urls.py:
url(r'^create/$', CreateContextView.as_view(),name='context_create'),
You can also achieve it like this:
forms.py:
class ContextForm(ModelForm):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user", None)
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.add_input(Submit('submit', 'Submit'))
super(ContextForm, self).__init__(*args, **kwargs)
class Meta:
model = Context
fields = ('title', 'description',)
def save(self, force_insert=False, force_update=False, commit=True):
obj = super(ContextForm, self).save(commit=False)
obj.user = self.user
if commit:
obj.save()
return obj
views.py
def some_view(request):
...
form = ContextForm(request.POST, user=request.user)
...
In your model :
from django.contrib.auth.models import User
class Context(models.Model):
title = models.CharField(max_length=32)
description = models.TextField()
user = models.OneToOneField(User)
in the view to associate with user:
if request.method == 'POST':
form = ContextForm(request.POST)
if form.is_valid():
user = User.objects.create_user(username=form.cleaned_data['username'], email = form.cleaned_data['email'], password = form.cleaned_data['password'])
user.save()
obj = Context(user=user, title=form.cleaned_data['title'], description=form.cleaned_data['description'])
obj.save()
If you need to ask for login ,just add #login_required
#login_required
def myfunction(request):
...
and take a look at this tutorial
EDIT
I've seen your edited question,You need to add this to your urls.py
TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.request',
)
This means that it will be passed to all templates that are rendered using a RequestContext, which all generic views are.