I would like to retrieve a list of all users online on my, online meaning they have carried out some sort of activity on the app over the last 2 minutes.
I am noticing however, that my list only shows people who have logged in as an admin (atleast from what I am observing).
I am using the package django-online-users for this
I created a view with a queryset that would retrieve all online users who have carried out some activity on the app
class OnlineUsers(viewsets.ModelViewSet):
queryset = online_users.models.OnlineUserActivity.get_user_activities(timedelta(seconds=120))
serializer_class = OnlineUsersSerializer
My OnlineUsersSerializer class:
class OnlineUsersSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = '__all__'
This is my profile model:
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
city = models.CharField(max_length=50,blank=True)
country = models.CharField(max_length=50, blank=True)
bio = models.CharField(max_length=500, blank=True)
profile_pic = models.ImageField(upload_to='profile/%Y/%m/%d',
default='media/placeholder.png',
blank=False,
null=False)
#we are hooking create_user_profile and save_user profile methods to the User model whenever a save event occurs. This kind of signal is called post_save
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
I created an API end-point that I would use to retrieve all online users from my React app:
path('online_users',OnlineUsers.as_view({'get':'list'}), name='online_users'),
Regardless of whether I login with a different user, I am only able to see one user (my admin account).
UPDATE
Based on the top answer I received this is what I tried:
class OnlineNowMixin:
def initial(self, request, *args, **kwargs):
super().initial(request, *args, **kwargs)
user = request.user
if not user.is_authenticated:
return
online_users.models.OnlineUserActivity.update_user_activity(user)
I added this class to all my viewsets:
class OnlineUsers(OnlineNowMixin, viewsets.ModelViewSet):
...
class UserViewSet(OnlineNowMixin, viewsets.ModelViewSet):
...
class UpdateProfileView(OnlineNowMixin, generics.UpdateAPIView):
...
class ProgrammingChallengeView(OnlineNowMixin, ReadOnlyModelViewSet):
...
class GetAllUsers(OnlineNowMixin, viewsets.ModelViewSet):
...
However, the API end point I created still only shows users who logged as opposed to user who have been logged in through authentication (ie. received an access & refresh token).
This is what i am using for authentication:
class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
#classmethod
def get_token(cls, user):
token = super().get_token(user)
token['username'] = user.username
token['first_name'] = user.first_name
token['last_name'] = user.last_name
token['country'] = user.profile.country
token['city'] = user.profile.city
token['bio'] = user.profile.bio
token['photo'] = json.dumps(str(user.profile.profile_pic))
return token
class CustomTokenObtainPairView(TokenObtainPairView):
serializer_class = CustomTokenObtainPairSerializer
Your users aren't processed as online by the middleware probably because you are using Django Rest Framework (DRF). DRF performs the authentication on the view layer, out of the various builtin authentication classes only SessionAuthentication will have the user authenticated at the middleware level.
What you can do to solve this problem is create a mixin to be used by all your views / viewsets. This mixin should override the initial method and do what the OnlineNowMiddleware from django-online-users does:
from online_users.models import OnlineUserActivity
class OnlineNowMixin:
def initial(self, request, *args, **kwargs):
super().initial(request, *args, **kwargs)
user = request.user
if not user.is_authenticated:
return
OnlineUserActivity.update_user_activity(user)
class OnlineUsers(OnlineNowMixin, viewsets.ModelViewSet):
...
Related
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?
I'm trying to update Profile model, which extends the default User model. But something clearly off because Profile displays no entries. Could you please suggest how to properly update model in middleware that extends another one?
models.py
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
last_activity = models.DateTimeField() # filled in by middleware
def __str__(self):
return str(self.id)
serializers.py
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = '__all__'
view.py
class ProfileList(ListAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
middleware.py
from django.utils import timezone
class UpdateLastActivityMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
def process_view(self, request, view_func, view_args, view_kwargs):
if request.user.is_authenticated():
# Update last visit time after request finished processing.
# I'm trying to create record - in case user had no activity before
Profile.objects.filter(user__id=request.user.pk).update_or_create(last_activity=timezone.now()) # <-- looks like something is off here, since I'm not sure how to correctly update extended model
settings.py
MIDDLEWARE = [
...
'core.middleware.UpdateLastActivityMiddleware',
]
Profile.objects.filter(user__id=request.user.pk).update_or_create(last_activity=timezone.now())
with
user.profile.last_activity=timezone.now()
user.save()
Reason being that since it's a one to one relationship you do not need update_or_create, since a user will always have a profile and the reverse relation should work out of the box and you'd be able to just update the property last_activity directly.
This is the solution I've ended up with thanks to #Borko Kovacev and a lot of googling:
middleware.py
class UpdateLastActivityMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
def process_view(self, request, view_func, view_args, view_kwargs):
assert hasattr(request, 'user')
# need to separately detect user when used middleware with JWT (else Anonymous User)
try:
auth_res = authentication.JWTAuthentication().authenticate(request)
except InvalidToken:
return JsonResponse({'error': 'Invalid token'}, status=status.HTTP_401_UNAUTHORIZED)
if auth_res:
request.user = auth_res[0]
# save last_activity on each authenticated request
if request.user.is_authenticated:
request.user.profile.last_activity = timezone.now() # assigning last_activity
request.user.save() # this triggers signal on User model saving
print(f'MIDDLEWARE saved last_activity for {request.user}')
signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import User
from .models import Profile
#receiver(post_save, sender=User) # to populate Profile on user creation (else fails because Profile record does not exist for user)
def create_profile(sender, instance, created, **kwargs):
if created:
print('signals create_profile ')
Profile.objects.create(user=instance)
#receiver(post_save, sender=User) # to update Profile on user update (triggered by middleware)
def save_profile(sender, instance, **kwargs):
print('signals save_profile ')
instance.profile.save()
See all users (that don't have search_hidden enabled) view
#login_required
def users(request):
"""List all users page"""
t_users = User.objects.all()
users = t_users.usersettings.filter(search_hidden=False).select_related('user')
context = {'users': users}
return render(request, 'users/users.html', context)
UserSettings model
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
class UserSettings(models.Model):
"""Stores the user's settings."""
user = models.OneToOneField(User, related_name='usersettings', on_delete=models.CASCADE)
public_profile = models.BooleanField(default=True)
search_hidden = models.BooleanField(default=False)
class Meta:
verbose_name_plural = 'usersettings'
def __str__(self):
return f"{self.user}'s settings"
#receiver(post_save, sender=User)
def create_user_usersettings(sender, instance, created, **kwargs):
if created:
UserSettings.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_usersettings(sender, instance, **kwargs):
instance.usersettings.save()
All users have a UserSettings model tied to them when their accounts are created. In my view, I want to select all users that have search_hidden disabled, however what I've tried doesn't work.The error 'QuerySet' object has no attribute 'usersettings' is displayed whenever the page is requested. I probably need to select each user and retrieve the settings, but I don't know how to do that in an efficient manner.
iliya commented that filtering using t_users.objects.filter(search_hidden=False) would return users where search_hidden is not true in their settings object.
I am using the authtools plugin to manage the user and its profile in django, but when I create the user, it does not create its profile, I have to go to the admin part of the site and create it manually.
I separated the applications into account and profile.
This is the profiles model:
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
primary_key=True)
slug = models.UUIDField(default=uuid.uuid4, blank=True, editable=False)
email_verified = models.BooleanField("Email verified", default=True)
This is the signal.py, that is inside of the profiles application:
#receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_profile_handler(sender, instance, created, **kwargs):
if not created:
return
profile = models.Profile(user=instance)
profile.save()
logger.info('New user profile for {} created'.format(instance))
This is the admin.py of the account app:
class UserProfileInline(admin.StackedInline):
model = Profile
class NewUserAdmin(NamedUserAdmin):
inlines = [UserProfileInline]
list_display = ('is_active', 'email', 'name', 'permalink',
'is_superuser', 'is_staff',)
# 'View on site' didn't work since the original User model needs to
# have get_absolute_url defined. So showing on the list display
# was a workaround.
def permalink(self, obj):
url = reverse("profiles:show",
kwargs={"slug": obj.profile.slug})
# Unicode hex b6 is the Pilcrow sign
return format_html('{}'.format(url, '\xb6'))
admin.site.unregister(User)
admin.site.register(User, NewUserAdmin)
admin.site.register(Profile)
When I signup a user, both the user and the profile objects are created, only they are not linked. Why is that?
Thank you
Use this below your profile model in models.py. I hope you are generating slug by another slugify signal.
def user_created_receiver(sender, instance, created, *args, **kwargs):
if created:
Profile.objects.get_or_create(user = instance)
post_save.connect(user_created_receiver, sender = User)
I am using Django 1.10.*. This is my user profile model:
class student(models.Model):
user = models.OneToOneField(User, primary_key=True, on_delete=models.CASCADE)
state = models.CharField(max_length=21, null=True, blank=True, choices=in_states.STATE_CHOICES)
city = models.CharField(max_length=21, null=True, blank=True)
date_joined = models.DateTimeField(default=timezone.now)
educational_role = models.CharField(max_length=39, choices=EDUCATIONAL_ROLE)
institute = models.ForeignKey(educational_institute, null=True, blank=True)
language = models.CharField(max_length=8, choices=LANGUAGES)
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
student.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
When I created form class for student and use that to create a View class extending FormView class in my Views passing the form context to HTML template like this:
forms.py :
class RegistrationForm(forms.ModelForm):
class Meta:
model = student
fields = ['user', 'state', 'city', 'educational_role', 'institute', 'language']
views.py :
class Register(FormView):
template_name = 'searcher/register.html'
form_class = RegistrationForm
def get_context_data(self, **kwargs):
context = super(Register, self).get_context_data(**kwargs)
context['applink'] = applink
context['forumlink'] = forumlink
return context
So how do I need to modify this so that the registration form asks for username, firstname, email of User model and also the fields added by student model and having option for creating a new educational_institute if it doesn't exist already?
You could use multi-table inheritance django.contrib.auth.models.User. Would look like this:
from django.contrib.auth.models import User
class Student(User):
state = models.CharField(...)
...
This way all Django features like ModelForm or UpdateView should function without any additional fiddling. In behind, Django will just create exactly the same OneToOneField for you.
And if all of your users are students you could also replace the auth user model entirely.
If you don't want to do any of those, you will need to add additional fields into your form, specify them if you form Meta, take care of setting their default values (in __init__ in example), cleaning these fields and saving the model behind OneToOneField all by yourself.