How to pass a user instance in a class-based view - django

I would like to pass the user instance of the current user to a variable in a class-based view and feed it in a second step, within a python script, to a database table column defined as author = models.ForeignKey(User, on_delete=models.PROTECT). Related to that column I get the following integrity error: NOT NULL constraint failed.. From the traceback I can see that my variable loggedUser, which I had attributed self.request.user, is a SimpleLazyObject.
I have found another question similar to mine. But it discusses the issue related to functions - I am having difficulties to adapt the answers to my case of a class-based view.
How can I pass my user instance?
Thank you!
models.py
from django.db import models
from django.contrib.auth.models import User
class Data(models.Model):
temperature = models.FloatField(max_length=5)
author = models.ForeignKey(User, on_delete=models.PROTECT)
views.py
from . import apythonscript
from django.views.generic import TemplateView
class PlotGraphView(TemplateView):
def get_context_data(self, **kwargs):
loggedUser = self.request.user
context['plot'] = apythonscript.putDataInDB(loggedUser)
return context
apythonscript.py
from app.models import Data
def putDataInDB(loggedUser):
mydata=json.loads(response)
for line in mydata['feeds']:
# try:
data_handle = Data(temperature=line['field1'], author=loggedUser,)
data_handle.save()
# except:
# continue
traceback
link to traceback

to get the currently logged in user in Django, you can simply use, request.user. In CBV, request is a class object, so you should use self.request to access request data.
example
class PlotGraphView(LoginRequiredMixin, TemplateView):
# your stuff
def get_current_user(self): # this is just a custom function, you don't
return self.request.user
So, anywhere in the CBV's scope, you can access the current user by self.request.user

Related

Associate user to a post in Django

hi everyone i'm trying to make a blog and i want to associate a user to post .. and to comment , it's a multi-user blog and i don't know how to do it , any help guys ? !
here is the model file :
from django.db import models
from django.utils import timezone
from django.conf import settings
from django.utils.text import slugify
# Create your models here.
#this is for categories
class Category(models.Model):
title=models.CharField(max_length=100,default='')
def __str__(self):
return self.title
#this is where a user can create his own gigs
class Gigposter(models.Model):
title=models.CharField(default='',max_length=100,blank=False)
user=models.OneToOneField(settings.AUTH_USER_MODEL,on_delete=models.CASCADE,null=False)
categories=models.OneToOneField(Category,on_delete=models.PROTECT,default='',null=False)
published_at=models.DateTimeField(auto_now_add=True)
description=models.TextField(default='',max_length=None,blank=False)
mainphoto=models.ImageField(default='')
photo=models.ImageField()
def __str__(self):
return self.title
#this is where a user can comment and say what he thinks about others work
class Comment_area(models.Model):
user=models.OneToOneField(settings.AUTH_USER_MODEL,on_delete=models.CASCADE,blank=False)
comment=models.TextField(max_length=None,default='')
commented_at=models.DateTimeField(auto_now_add=True)
and the views file is empty as you can see :
from django.shortcuts import render
# Create your views here.
Wouldn't recommend using a OneToOneField here, as it tells Django the user is tied to exactly one comment/post (when, a user is likely to post more than once).
You could use models.ForeignKey:
from django.contrib.auth.models import User
class Gigposter(models.Model):
# Other properties...
user = models.ForeignKey(
'User'
on_delete=models.CASCADE
)
If you want to automatically associate a user to a post when working on the admin page, you should redefine save_model method of your model. This method describes everything what should be done when you save your model. In your case you should add something like
class GigposterAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
obj.user = `request.user`
super().save_model(request, obj, form, change)
admin.site.register(Gigposter, GigposterAdmin)
to your admin.py. You should also exclude the user field from fieldset in GigposterAdmin. See this for reference.
If you need to identify user in your views, you can always use request.user. In particular, you can pass it as a part of the context for generating a view. Hope this helps.

Got a `TypeError` when calling `Article.objects.create()`

I am working on Django React Project using the Django REST FRAMEWORK,I am trying to post some data tied to my model.
The list view and the detail view of the project works pretty fine,The only problem is when I try to make a POST request.
Whenever I Try post the data in the CreateAPIView I get an error :
Got a `TypeError` when calling `Article.objects.create()`. This may be because
you have a writable field on the serializer class that is not a valid argument to
`Article.objects.create()`. You may need to make the field read-only, or override
the ArticleSerializer.create() method to handle this correctly.
I have searched through various past problems but non of them seem to fix my problem.
Here is my serializers file:
from rest_framework import serializers
from articles.models import Article
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = ('id','title','content','star_count','like_count','comment_count','avatar')
Here is my views file
from rest_framework.generics import ListAPIView,RetrieveAPIView,CreateAPIView,UpdateAPIView,DestroyAPIView
from .serializers import ArticleSerializer
from articles.models import Article
from rest_framework import viewsets
class ArticleViewSets(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
models file
content = models.TextField()
comment_count = models.IntegerField(default=0,null=True,blank=True)
like_count = models.IntegerField(default=0,null=True,blank=True)
star_count = models.IntegerField(default=0,null=True,blank=True)
avatar = models.ImageField(null=True,blank=True)
def __str__(self):
return self.title
def save(self):
if not self.slug:
self.slug = slugify(self.title)
super(Article,self).save()
Here is the error generated when I try to make a POST request based on the django rest framework createAPIVew
Got a `TypeError` when calling `Article.objects.create()`. This may be because you have a writable field on the serializer class that is not a valid argument to `Article.objects.create()`. You may need to make the field read-only, or override the ArticleSerializer.create() method to handle this correctly.

How to prevent the logged in user to access the profile of other user by simply changing the id in url , in django?

I am currently building a small project using Django, I have noticed a problem that a logged in user was getting access to the other users page by simply changing the id in the url i.e
This is the url of currently logged in user
http://localhost:8000/home/myBooks/7/
by changing that id from 7 to 6
i.e
http://localhost:8000/home/myBooks/6/
He was getting access to that page,I have used #login_required for functional based views and LoginRequiredMixin for class based views ,but they are not helping, what else I need to do to prevent this problem?
My app/views.py:
from django.shortcuts import render,redirect
from django.http import HttpResponse
from django.views.generic.edit import FormView
from . forms import BookForm
from django.contrib.auth.models import User
from . models import UserBooks
from django.contrib.auth.models import User
from django.views import generic
from django.contrib.auth.decorators import login_required
from .models import UserBooks
from django.shortcuts import get_object_or_404
from django.contrib.auth.mixins import LoginRequiredMixin
#login_required
def HomeView(request):
return render(request,'home/homepage.html')
class BookDetailsView (LoginRequiredMixin,generic.DetailView):
model=UserBooks
template_name='home/bookdetails.html'
class BooksView (LoginRequiredMixin,generic.DetailView):
model=User
template_name='home/mybooks.html'
#login_required
def addBooks(request):
if (request.method=='POST'):
form=BookForm(data=request.POST)
if(form.is_valid()):
u=UserBooks()
u.book_name=form.cleaned_data['book_name']
u.book_author=form.cleaned_data['book_author']
u.book_ISBN=form.cleaned_data['book_ISBN']
u.book_status=True
u.book_genre=form.cleaned_data['book_genre']
u.username=request.user.username
u.user_id = User.objects.get(username=request.user.username)
u.save()
return redirect('/')
else:
form = BookForm()
return render (request,'home/addbooks.html',{'form':form})
my apps/models.py:
from django.db import models
from django.contrib.auth.models import User
class UserBooks(models.Model):
user_id = models.ForeignKey(User,on_delete=models.CASCADE,null=True)
username = models.CharField(max_length=200)
book_name = models.CharField(max_length=200)
book_author = models.CharField(max_length=200)
book_ISBN=models.CharField(max_length=200)
book_genre = models.CharField(max_length=200)
book_status=models.BooleanField(default=False)
class Meta:
unique_together = (("username", "book_ISBN"),)
def __str__(self):
return self.book_name
my apps/urls.py:
from django.urls import path
from . import views
app_name='home'
urlpatterns=[
path('',views.HomeView,name='home'),
path('addBooks/',views.addBooks,name='addBooks'),
path('myBooks/<int:pk>/',views.BooksView.as_view(),name='myBooks'),
path('<int:pk>/', views.BookDetailsView.as_view(), name='myBooks'),
]
If your view should always show the detail for the current user, don't put the ID in the URL at all; get the logged-in user directly within the view.
class BooksView(LoginRequiredMixin, generic.DetailView):
model = User
template_name ='home/mybooks.html'
def get_object(self):
return self.request.user
...
path('myBooks/',views.BooksView.as_view(),name='myBooks'),
class BooksView(LoginRequiredMixin, DetailView):
...
def get(self, request, *args, **kwargs):
current_user = User.objects.get(id=self.request.user.pk)
if current_user.pk == kwargs['pk']:
return HttpResponseRedirect('/')
else:
return HttpResponseRedirect('profile-url')
Here I assume that if you are logged in user and you try to check another user profile by giving id in url. So I add a get method which will check is requested URL id is for the current user (books/7/ is 7 is current user id) if not then redirect to an URL, for example, otherwise redirects to another url. You can get some idea. This may not help you exactly.
If you have just started to develop the app, then it is okay to use pks inside of urls. However, when it comes to a real working app it can lead to some security problems.
As you wrote, one can simply change the url and get some private data.
Other problems can be:
The number of users in the database can be easily counted by iteration through your urls.
The user can be easily detected by his id. Knowing it one can easily get some private data.
If you will decide to change ids in your db, then all the external links will be broken...and etc.
Considering that I suggest an approach in which you use ids internally. For external usage (urls, links) you can use uuids.
To do that you just need additional field into your model:
import uuid
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
And here is the example url:
url(r'^myBooks/(?P<user_uuid>\b[0-9A-Fa-f]{8}\b(-\b[0-9A-Fa-f]{4}\b){3}-\b[0-9A-Fa-f]{12}\b)/$',
After you switch to uuids it will be almost impossible to "hack" the url.

How do I present data relevant to the logged-in user based on Group?

I'm a total newbie to django so this may well have an obvious answer but so far google hasn't worked out for me.
I have this skeleton application using Django 1.8.
I have a simple model that has an owner field which is a ForeignKey to Group.
When a user is logged in I would like to show only the items that he/she has access to. Access being determined by the fact that the user belongs to the same group.
model.py
class Device(models.Model):
name = models.CharField(max_length=100,db_index=True)
owner = models.ForeignKey(Group)
def __str__(self):
return self.name
views.py
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views import generic
from .models import Device
from django.contrib.auth.models import Group, User
class IndexView(generic.ListView):
"""
This renders the index page listing the devices a user can view
"""
template_name = 'devices/index.html'
context_object_name = 'devices_list'
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(IndexView, self).dispatch(*args, **kwargs)
def get_queryset(self):
"""
Return the devices visible to the logged-in user
"""
return devices=Device.objects.all()
What I don't seem to be able to figure out is what to put in the .filter() instead of .all() call in my get_queryset method.
Updated based on Jean-Michel's feedback.
I don't have a Django environment in front of me at the moment, but this might be a good start:
return devices=Device.objects.filter(owner=self.request.user.groups.all())
Alternatively, Django's ORM uses double underscore (__) to access field lookups. These can be used to get values greater than (__gt), or in a list (__in) amongst other lookups (see the docs).
return devices=Device.objects.filter(owner__in=self.request.user.groups.all())
This kind of depends on where the user object is located. I'm assuming the logged in user is kept as a class attribute, i.e., self.user. Per, Jean-Michel's comments, the user object is attached to the request. So we can access it from self.request.user.groups.
Finally, you can access specific fields on models using the double underscore notation as well (__), this example is from the docs:
# Find all Articles for any Reporter whose first name is "John".
>>> Article.objects.filter(reporter__first_name='John')
[<Article: John's second story>, <Article: This is a test>]

How to limit access to the UpdateView of an object to the creator of that object

Django and programming noob here. I've made an application I'd like to deploy, but I need to figure out how to limit access to the UpdateView to the creator of that object, and I'm stumped.
Currently a user can use the CreateView .../universities/create/ to create a university object, but then any user can use .../universities/update/ to edit that object. I want to configure this so only the user who is the creator (any user with the ManytoMany attribute 'administrator') of that university has access to the UpdateView for their university object.
Any advice would be appreciated. I've spent a few days on this and I haven't made much traction...thanks for reading.
models.py
class University(models.Model):
name = models.CharField(max_length=100)
about = models.TextField()
administrators = models.ManyToManyField(User)
profile_picture = models.FileField(upload_to=get_upload_file_name, blank=True)
def __unicode__(self):
return unicode(self.name)
def get_absolute_url(self):
return reverse('university_detail', kwargs={'pk': str(self.id)})
views.py
class UniversityCreateView(CreateView):
model = University
form_class = UniversityForm
template_name = 'university_create.html'
def form_valid(self, form):
f = form.save(commit=False)
f.save()
return super(UniversityCreateView, self).form_valid(form)
class UniversityUpdateView(UpdateView):
model = University
form_class = UniversityForm
template_name='university_form.html'
You can use UserPassesTestMixin as the documentation says:
limit access based on certain permissions or some other test
just implement test_func(self) that returns True if the user should enter the view.
You might write a code like this:
class UniversityUpdateView(UserPassesTestMixin,UpdateView):
def test_func(self):
return self.request.user.administrators_set.filter(pk=self.get_object().pk).exists()
model = University
form_class = UniversityForm
template_name='university_form.html'
youll have to include permission decorators on your views , further info is here https://docs.djangoproject.com/en/dev/topics/auth/ , & https://docs.djangoproject.com/en/dev/topics/auth/default/#topic-authorization
so if you want to limit your updateview to any user with the ManytoMany attribute 'administrator', youll have to do something like this:
views.py
from appname.users.decorators import requiresGroup
from django.contrib.auth.decorators import login_required
class UniversityUpdateView(UpdateView):
model = University
form_class = UniversityForm
template_name='university_form.html'
#method_decorator(requiresGroup("groupname" , login_url='/accounts/login/'))
def dispatch(self, request, *args, **kwargs):
return super(UniversityUpdateView, self).dispatch(request, *args, **kwargs)
also if you havent already youll have to include the following at the top of your models.py
from django.contrib.auth.modes import user
though Ill assume its there as youve defined your administrators with the user model
then go to the group seetings in the django admin ( should be a url like localhost/admin/auth/group , add your special adminstrator group name, then go to the admin user section (localhost/admin/auth/user), then make sure they have been put into the adminstrator group
then replace "groupname" in the #requiresGroup decorator with the actual name of the user group
the #requiresGroup decorator isnt a standard decorator, so it has to be written
make a folder path and file like appname/users.decorators.py
then in decorators.py write
from functools import update_wrapper , wraps
from django.utils.decorators import available_attrs
from django.http import HttpResponse, HttpResponseRedirect
def requiresGroup(groupname):
def decorator(view_function):
def _wrapped_view(request,*args,**kwargs):
if request.user.groups.filter(name=groupname).count()!=1:
return HttpResponseRedirect("/")
else:
return view_function(request,*args,**kwargs)
return wraps(view_function,assigned=available_attrs(view_function))(_wrapped_view)
return decorator
hope this helped
edit: made a mistake, put the decorators above the class, they should be in a function inside the class, noticed my mistake almost immediately so hopefully I havent caused any trouble
You can override the get method of your class based view (in this case UniversityUpdateView). Then in the method check if user has rights to access the page and if not raise exception or redirect the user to another page. If the user has enough rights to access the page then just let the normal behavior go on.
class UniversityUpdateView(UpdateView):
model = University
form_class = UniversityForm
template_name='university_form.html'
def get(self, request, *args, **kwargs):
if request.user.groups.filter(name=groupname).count()!=1:
return HttpResponseRedirect("/")
return super().get(request, *args, **kwargs)