add more model info to the JWT token - python

I'm creating a messaging app. I have 3 models in my backend Django. I have a profile model that stores user & which room they are connected with(so that every time they log in, their rooms will pop up in side bar like WhatsApp). in profile model I have a many to many relationship with Room model that stores rooms list. As I'm using JWT web token for authentication, I want users profile model/rooms like of that user to be added in the token. so that I can fetch the info from token directly but I don't know how to add that fields info into the token views. I've already customized my token obtain view where I added users name as extra but I need to add the list of rooms too.
Thanks in advance for helping.
#model.py
from django.db import models
from django.contrib.auth.models import User
from django.dispatch import receiver
from django.contrib.auth.models import User
# Create your models here.
class Room(models.Model):
name = models.CharField(max_length=100,blank=True,null=True)
class Profile(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE)
rooms = models.ManyToManyField(Room)
class Message(models.Model):
user = models.ForeignKey(User,on_delete=models.CASCADE,blank=False,null=True)
message = models.TextField(max_length=500,blank=False,null=True)
name = models.CharField(max_length=100,blank=True,null=True)
room = models.ForeignKey(Room,on_delete=models.CASCADE,null=True)
time = models.DateTimeField(auto_now_add=True)
received = models.BooleanField(default=False,null=True)
#views.py
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework_simplejwt.views import TokenObtainPairView
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
#classmethod
def get_token(cls, user):
token = super().get_token(user)
token['name'] = user.username
**here i want to have a token['room'] that will return me the list of rooms**
return token
class MyTokenObtainPairView(TokenObtainPairView):
serializer_class = MyTokenObtainPairSerializer

At first get the Profile: without reverse relation it can be retrieved via
profile = Profile.objects.get(user=user)
Then get the rooms:
rooms = profile.rooms.all()
At the end you should consider what information about the rooms you are storing into the token. The name can be blank and does not need to be unique. So it is better to store the id:
token['rooms'] = [r.id for r in rooms]

well, i found out by myself. it's very easy. just get all the profile data of that user. now iterate through the list of the rooms using rooms.all()
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
#classmethod
def get_token(cls, user):
token = super().get_token(user)
# Add custom claims
token['name'] = user.username
room = Profile.objects.get(user=user)
token['rooms'] = [[r.name,r.id] for r in room.rooms.all()]
print([r for r in room.rooms.all()])
# ...
return token

Related

DRF - Prevent users from referencing objects that do not belong to them

I have two models like so with a parent child relation:
models.py
class Bank(...):
user = models.ForeignKey('User', ...)
class Account(...)
bank = models.ForeignKey('Bank', ...)
user = models.ForeignKey('User', ...)
I am using DRF and want to provide API access to these models. I want to ensure that Users can only access their own data. On the viewsets I can retrict the querysets to just the objects the user "owns" like so:
views.py
class BankViewSet(...):
def get_queryset(self):
return self.queryset.filter(
user = request.user
)
And I can do the same for Accounts.
However, how can I stop a user from creating an Account via POST request with a Bank that they do not own? I want to ensure that users can only create Accounts that belong to a Bank that they own.
How can I enforce/check that the Bank in the Account POST request contains the same user as the requestor?
You can create a field-level validation on the AccountSerializer class, as
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = (
"bank",
"field_1",
"field_2"
)
def validate_bank(self, bank_instance: Bank):
if bank_instance.user == self.context["request"].user:
return bank_instance
raise serializers.ValidationError("Not belongs to you!!!")

Edit permissions for Django Details View for api calls

I am very new to django and currently trying to generate api calls to localhost:8000/stateapi/id where id is an id for a single "State" object in a json (like 1, 2, etc).
It is using token authentication by passing a username and password to the "get-token" endpoint and then using that token for calls to the stateapi endpoint.
I mostly followed this tutorial https://scotch.io/tutorials/build-a-rest-api-with-django-a-test-driven-approach-part-2
and keep getting a "detail": "You do not have permission to perform this action."
Here are the views where CreateView handles the "localhost:8000/stateapi" endpoint and DetailsView handles the localhost:8000/stateapi/id endpoint.
class CreateView(generics.ListCreateAPIView):
queryset = State.objects.all()
serializer_class = StateSerializer
permission_classes = (permissions.IsAuthenticated,IsOwner)
def perform_create(self, serializer):
"""Save the post data when creating a new State."""
serializer.save(owner=self.request.user)
class DetailsView(generics.RetrieveUpdateDestroyAPIView):
"""This class handles the http GET, PUT and DELETE requests."""
queryset = State.objects.all()
serializer_class = StateSerializer
permission_classes = (permissions.IsAuthenticated,IsOwner)
I can't seem to figure out why the authenticated user has permission to access information from CreateView but not DetailsView.
Here is the permissions code:
class IsOwner(BasePermission):
"""Custom permission class to allow only bucketlist owners to edit them."""
def has_object_permission(self, request, view, obj):
# Our problem is that we do not have a owner property for the object
"""Return True if permission is granted to the bucketlist owner."""
return obj.owner == request.user
Upon testing what happens when DetailsView is called, i've found that obj.owner is "None" when DetailsView is called and obj.owner is correctly equal to request.user whenever CreateView is called which would explain why the authenticated user can make get requests to the endpoint without an id while it cannot for the endpoint with an id.
Are there an suggestions as to how I could either:
a) make sure obj has the correct "owner" property (something that CreateView is doing but DetailsView is not)
b) change my permissions in some way
c) something else I cannot think of.
Thanks!
Can you share your State model and StateSerializer – Iain Shelvington Jun 18 at 3:26
State Model:
from django.db import models
from django.db.models.signals import post_save
from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token
from django.dispatch import receiver
# Create your models here.
# 1 is /, 2 is -, 3 is (, 4 is ), 5 is .
class State(models.Model):
STATE = models.CharField(max_length=30,blank=True,null=True)
Team_Contact = models.CharField(max_length=100,blank=True,null=True)
CONTACT_INFORMATION = models.TextField(blank=True,null=True)
LEGISLATION1EXECUTIVE_ORDER = models.TextField(blank=True,null=True)
TESTING = models.TextField(blank=True,null=True)
TESTING1DEPLOYMENT_REQUIREMENTS_3SELF_CERTIFICATION4 = models.TextField(blank=True,null=True)
PRE2EMPTION = models.TextField(blank=True,null=True)
owner = models.ForeignKey('auth.User', related_name='statelists', on_delete=models.CASCADE,blank=True,null=True)
OVERSIGHT_DEPARTMENT = models.TextField(blank=True,null=True)
INFRASTRUCTURE_DEVELOPMENTS = models.TextField(blank=True,null=True)
CRASHES1SAFETY_INCIDENTS = models.TextField(blank=True,null=True)
DATA1PRIVACY_CONCERNS = models.TextField(blank=True,null=True)
PUBLIC_EDUCATION_FOR_AVS = models.TextField(blank=True,null=True)
LIABILITY1INSURANCE_REQUIREMENTS = models.TextField(blank=True,null=True)
HEALTH1EQUITY_CONCERNS = models.TextField(blank=True,null=True)
MISC5 = models.TextField(blank=True,null=True)
def __str__(self):
"""Return a human readable representation of the model instance."""
return "{}".format(self.STATE)
# This receiver handles token creation immediately a new user is created.
#receiver(post_save, sender=User)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if created:
Token.objects.create(user=instance)
Serializers:
from rest_framework import serializers
from .models import State
from django.contrib.auth.models import User
class StateSerializer(serializers.ModelSerializer):
"""Serializer to map the Model instance into JSON format."""
# understand exactly what this line does
owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
"""Meta class to map serializer's fields with the model fields."""
model = State
fields = ('id','STATE','Team_Contact','CONTACT_INFORMATION','LEGISLATION1EXECUTIVE_ORDER','TESTING',
'TESTING1DEPLOYMENT_REQUIREMENTS_3SELF_CERTIFICATION4','PRE2EMPTION','OVERSIGHT_DEPARTMENT','INFRASTRUCTURE_DEVELOPMENTS',
'CRASHES1SAFETY_INCIDENTS','DATA1PRIVACY_CONCERNS','PUBLIC_EDUCATION_FOR_AVS','LIABILITY1INSURANCE_REQUIREMENTS',
'HEALTH1EQUITY_CONCERNS','MISC5', 'owner')
read_only_fields = ('STATE', 'Team_Contact','CONTACT_INFORMATION')
class UserSerializer(serializers.ModelSerializer):
"""A user serializer to aid in authentication and authorization."""
states = serializers.PrimaryKeyRelatedField(
many=True, queryset=State.objects.all())
class Meta:
"""Map this serializer to the default django user model."""
model = User
fields = ('id', 'username', 'states')
All things here seem to be normal. I think the problem comes from other parts of your code? Or your checked object actually doesn't have any owner linked to it?

How to Restricting one like per post in Django

I am a beginner in Django currently. I want to restrict one like per post in my so Posting kind of app, where you can post a text and user can like or dislike the same. Now I enabled Login and I want a logged in user to like a post only once and I am unsuccessful in doing so.
Models.py
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class Todo(models.Model):
text = models.CharField(max_length=100)
complete = models.BooleanField(default=False)
like=models.IntegerField(default=0)
user = models.ForeignKey(User)
def __str__(self):
return self.text
Views,py
def like(request,todo_id):
if Todo.objects.filter(todo_id = todo_id, user_id=request.user.id).exists():
return redirect('index')
else:
todo = Todo.objects.get(pk=todo_id)
todo.like += 1
todo.save()
return redirect('index')
What basically I was missing is saving the likes corresponding to user. And this problem is easily solved by creating a model with User and main model as Foreign Key's as pointed out by Paolo.
models.py
from django.db import models
from django.contrib.auth.models import User
class Todo(models.Model):
text = models.CharField(max_length=100)
complete = models.BooleanField(default=False)
def __str__(self):
return self.text
class Todo_likes(models.Model):
todo = models.ForeignKey(Todo, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
views.py
from django.shortcuts import render,redirect
from .models import Todo,Todo_likes
from .forms import TodoForm
def like(request,todo_id):
user = request.user
todo = Todo.objects.get(id=todo_id)
like, created = Todo_likes.objects.get_or_create( # get_or_create will
# itself do the job of
# finding and creating if not exist
user = user,
todo = todo
)
if not created:
return redirect('index') #I don't wanted to show any error if existed earlier.
#I just wanted to redirect.
else:
return redirect('index')
And then counting my number of likes by simply going to my Index view and inserted
def index(request):
form = TodoForm()
todo_list = Todo.objects.order_by('id')
likes = Todo_likes.objects.count() # Adding this line
context= {'todo' : todo_list, 'form':form, 'likes' : likes}
return render(request,'todolistapp/index.html',context)
And displaying 'likes' object in my template.
I would like to give it a try and expand on my comment.
My suggestion is, to create a new model Todo_Likes which will have both Todo and User model as Foreign Keys to be able to track who liked a Todo.
models.py
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class Todo(models.Model):
text = models.CharField(max_length=100)
complete = models.BooleanField(default=False)
# Got rid of the user FK and like since I would count likes using
# Todo_Like.objects.count()
def __str__(self):
return self.text
class Todo_Like(models.Model):
todo = models.ForeignKey(Todo)
user = models.ForeignKey(User)
views.py
def like(request,todo_id):
# Get user
user = request.user
# Get Todo object
todo = Todo.objects.get(id=todo_id)
# Check/Validate if current user has already liked the Todo.
# If not create a new Todo_Like object.
todo_like = Todo_Like.objects.get(todo=todo, user=user)
if not todo_like:
# If the user haven't like the Todo object yet create a Todo_Like object.
t = Todo_Like (
todo=todo,
user=user
)
t.save()
else:
# If the user has already liked the Todo then proceed to do
# Whatever you want, redirect? message?
I would also like to answer a question in advance.
You might ask how would you count the likes now? Answer: Make use of Todo_Like.objects.count(), and make sure you count it by Todo.

Use of ._meta Django

I want to ask about the use of ._meta in this code ? I didn't find a documentation that explains the use of .meta
def resend_activation_email(self, request, queryset):
"""
Re-sends activation emails for the selected users.
Note that this will *only* send activation emails for users
who are eligible to activate; emails will not be sent to users
whose activation keys have expired or who have already
activated.
"""
if Site._meta.installed:
site = Site.objects.get_current()
else:
site = RequestSite(request)
for profile in queryset:
if not profile.activation_key_expired():
profile.send_activation_email(site)
resend_activation_email.short_description = _("Re-send activation emails")
_meta as the name implies stores the meta data of the model. Say, you want to loop over all the fields of a model, then you can use model._meta.get_fields().
By the way, meta options are covered in the documentation -
Model Meta options documentation
You can also use ._meta in testing.
Consider the below models.py:
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
content = models.TextField()
published = models.DateTimeField(default=timezone.now)
def __str__(self):
return f"{self.author} {self.title} {self.content}"
You could use ._meta to verify label names of a post (in test_models.py) like:
from datetime import datetime
from django.test import TestCase
from django.contrib.auth.models import User
from blog.models import Post
class TestBlogModels(TestCase):
def setUp(self):
author = User.objects.create(username='TestUser1')
self.post = Post.objects.create(
author=author,
title='Test Post Title 1',
content='Test content for title 1'
)
....
def test_blog_author_label(self):
author_field = Post._meta.get_field('author').verbose_name
self.assertEqual(author_field, "author")
def test_blog_title_label(self):
title_field = Post._meta.get_field('title').verbose_name
self.assertEqual(title_field, "title")
def test_blog_content_label(self):
content_field = Post._meta.get_field('content').verbose_name
self.assertEqual(content_field, "content")
def test_blog_title_max_length(self):
title_field_length = Post._meta.get_field('title').max_length
self.assertEqual(title_field_length, 100)
...
(verbose_name strips django.db.models.fields to the field speicified in models.py)

Connect to User Model in Django

Quick (probably foolish) question. This is the flow of my site: User logs in and is redirected to a custom admin page. On this admin page they have the ability to make a 'Profile'. I want to associate the Profile they create with their User data such that 1 User associates to 1 Profile.
For some reason the following isn't working (simply trying to associate
UserAdmin.Models
from django.db import models
from django.contrib.auth.models import User
class Profile(models.Model):
username = models.ForeignKey(User)
firstname = models.CharField(max_length=200)
lastname = models.CharField(max_length=200)
email = models.EmailField(max_length=200)
def __unicode__(self):
return self.username
UserAdmin.Views
def createprofile(request):
user = User.objects.get(id=1)
profile = Profile(username=user, firstname='Joe', lastname='Soe', email='Joe#Soe.com')
profile.save()
I keep getting: table useradmin_profile has no column named username_id
Any ideas? Appreciated.
EDIT:
Deleted my db and ran a fresh syncdb, changed to username = models.OneToOneField(User). Now I cam getting Cannot assign "u'superuser'": "Profile.username" must be a "User" instance.
UserAdmin.Models
from django.db import models
from django.contrib.auth.models import User
class Profile(models.Model):
user = models.OneToOneField(User)
def __unicode__(self):
return self.user.get_full_name()
UserAdmin.Views
def createprofile(request):
user_ = User.objects.get(pk=1)
profile = Profile(user=user_)
profile.user.first_name = 'Joe'
profile.user.last_name = 'Soe'
profile.user.email = 'Joe#Soe.com'
profile.user.save()
profile.save()
You syncdb'ed the Profile model before you had a username ForeignKey field. Django will only create tables but will not alter them once they have been created. Here an answer listing your options:
https://stackoverflow.com/a/7693297/990224.
And you should think about renaming username to user.

Categories

Resources