Update is_online field to true when user logs in - python

Building logic that, when a user logs in would change a boolean field in my Profile model to true and then turn that to false when the user logs out. The problem is I have an api_end point where I can see a list of all users and information related to said users.
If I login as two different users (using different browsers), When I console log their information both users are shown as online:
email: "user#gmail.com"
...
is_online: true
...
However, when I check the api_end point showing me the list of all users, only 1 user appears to be online (the last user I logged in as).
This is the view related to api_end point to view all users:
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all().order_by('-date_joined')
serializer_class = RegisterSerializer
#action(detail=True, methods=['POST'])
def set_password(self, request, pk=None):
user = self.get_object()
serializer = PasswordSerializer(data=request.data)
if serializer.is_valid():
user.set_password(serializer.validated_data['new_password'])
user.save()
return Response({'status': 'password set'})
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
This is my RegisterSerializer:
class RegisterSerializer(serializers.ModelSerializer):
profile = ProfileSerializer()
class Meta:
model = User
fields = ['username', 'email', 'password', 'first_name', 'last_name', 'profile']
extra_kwargs = {
'password': {'write_only': True},
}
def validate_password(self, value):
validate_password(value)
return value
def create(self,validated_data):
profile_data = validated_data.pop('profile')
user = User.objects.create(**validated_data)
Profile.objects.create(**profile_data, user=user)
return user
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)
is_online = models.BooleanField(default=False)
currently_active = models.BooleanField(default=False)
is_in_session = models.BooleanField(default=False)
#receiver(user_logged_in)
def got_online(sender, user, request, **kwargs):
user.profile.is_online = True
user.profile.save()
#receiver(user_logged_out)
def got_offline(sender, user, request, **kwargs):
user.profile.is_online = False
user.profile.save()
#receiver(post_save, sender=user)
def update_profile_signal(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
instance.profile.save()
This is the function that is triggered when a user logs in- this is where I set the user.profile.is_online field to true when a user logs in. I assume this is where the problem is coming from?:
class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
def validate(self, attrs):
authenticate_kwargs = {
self.username_field: attrs[self.username_field],
"password": attrs["password"],
}
try:
authenticate_kwargs["request"] = self.context["request"]
except KeyError:
pass
user = authenticate(**authenticate_kwargs)
if not user:
return {
'user': 'Username or password is incorrect',
}
token = RefreshToken.for_user(user)
# customizing token payload
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))
user_logged_in.send(sender=user.__class__, request=self.context['request'], user=user)
if not api_settings.USER_AUTHENTICATION_RULE(user):
raise exceptions.AuthenticationFailed(
self.error_messages["no_active_account"],
"no_active_account",
)
return {
'refresh': str(token),
'access': str(token.access_token),
}

Related

DRF: make a POST query without data

cannot manage a POST method-query to url /users/{pk}/subscribe
Accesing this url should insert a record in db using Subscription model. No data is passed in body when accessing this url
model.Subscription
class Subscription(models.Model):
user = models.ForeignKey(User,
on_delete=models.CASCADE,
related_name='subscriber',
)
author = models.ForeignKey(User,
on_delete=models.CASCADE,
related_name='subscribing'
)
a router to users
router.register(r'users', CustomUserViewSet, basename='users')
and the additional URL that accepts POST query in the CustomUserViewSet
class CustomUserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = CreateUserSerializer
#action(
detail=True,
methods=['post', 'delete'],
permission_classes=[IsAuthenticated],
)
def subscribe(self, request, *args, **kwargs):
user = request.user
author_pk = int(kwargs.get('pk'))
author_to_subscribe = get_object_or_404(User, id=author_pk)
if request.method == 'DELETE':
return Response(status=status.HTTP_204_NO_CONTENT)
if user.pk == author_pk:
data = {
"errors": "Cannot subscribe to yourself"
}
return Response(status=status.HTTP_400_BAD_REQUEST,
data=data)
# q = Subscription.objects.filter(user=user)
serializer = SubscriptionCreateSerializer(data=request.data)
serializer.is_valid()
serializer.save(user=user, author=author_to_subscribe)
return Response(
status=status.HTTP_200_OK)
Method shoould return serialized data from another serializer (SubscriptionSerializer. So i have 2 serializer for subscription: for viewing and creating
At this point in Postman getting error
class SubscriptionCreateSerializer(serializers.ModelSerializer):
class Meta:
model = Subscription
fields = '__all__'
def create(self, validated_data):
pass
class SubscriptionSerializer(serializers.ModelSerializer):
email = serializers.EmailField(source='author.email')
id = serializers.IntegerField(source='author.id')
username = serializers.CharField(source='author.username')
first_name = serializers.CharField(source='author.first_name')
last_name = serializers.CharField(source='author.last_name')
is_subscribed = serializers.BooleanField(default=True)
recipes = serializers.SerializerMethodField()
def get_recipes(self, obj):
qs = obj.author.recipes.all()
return RecipePublicSerializer(qs, many=True).data
def to_representation(self, instance):
representation = super().to_representation(instance)
representation['recipes_count'] = instance.author.recipes.count()
return representation
def to_internal_value(self, data):
recipes_data = data['recipes']
return super().to_internal_value(recipes_data)
class Meta:
model = Subscription
fields = ('email',
'id',
'username',
'first_name',
'last_name',
'is_subscribed',
'recipes',)
class CreateUserSerializer(serializers.Serializer):
"""Serializer for creating user objects"""
username = serializers.CharField(
validators=[
UniqueValidator(queryset=User.objects.all())
]
)
email = serializers.EmailField(
validators=[
UniqueValidator(queryset=User.objects.all())
]
)
first_name = serializers.CharField()
last_name = serializers.CharField()
password = serializers.CharField()
is_subscribed = serializers.SerializerMethodField()
#classmethod
def get_is_subscribed(self, object):
return True
def validate_username(self, value):
if value.lower() == "me":
raise serializers.ValidationError("Username 'me' is not valid")
return value
def create(self, validate_data):
user = User(**validate_data)
user.set_password(validate_data['password'])
user.save()
return user
class Meta:
fields = ('username',
'email',
'first_name',
'last_name',
'id',
'is_subscribed',)
read_only_fields = ('id',)
model = User
As I understood from question you would like to get user from request and id of author to subscribe from url. You pass to serializer empty body of your post request, but according to your serializer you should pass user and author fields, so to get serializer works you need to pass this parameters as data to serializer instance.
subscription_data = {
'user': request.user,
'author': author_to_subscribe
}
serializer = SubscriptionCreateSerializer(data=subscription_data)
serializer.is_valid(raise_exception=True)
serializer.save()
And also remove def save() method from your SubscriptionCreateSerializer it overwrites method which should put info about subscription in DB, so in your case it does nothing instead.

Django post save function not able to save role id in profile model

I am creating user in django, on post save using reciever signal to save the Profile, I have profile model like this:
class Profile(models.Model):
user = models.OneToOneField(base_settings.AUTH_USER_MODEL,on_delete=models.CASCADE)
has_sharkpod = models.BooleanField("The users organization has a sharkpod", default=False)
role = models.ForeignKey(Role, on_delete=models.DO_NOTHING, default=1)
def __str__(self):
return "Profile: " + self.user.username
This is my role model:
class Role(models.Model):
role_name = models.CharField(null=True, max_length=100) #unique = true
permissions = JSONField()
user = models.ManyToManyField(
base_settings.AUTH_USER_MODEL, blank=True)
date = models.DateTimeField(auto_now=True)
Now I want to save role as well that is passed in the request body of user creation, But I am not able to do that.
This is my post save function for profile:
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
Update
My User ViewSet:
class CreateUserViewSet(viewsets.ModelViewSet):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAdminUser, IsAuthenticated]
serializer_class = UserSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.save()
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data,
})
And my user serializer:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'email', 'first_name',
'last_name', 'id', 'is_active', 'password', 'profile')
read_only_fields = ('profile', 'is_active', )
def create(self, validated_data):
user = User.objects.create_user(**validated_data)
password = validated_data.pop('password')
user.set_password(password)
return user
I am Posting this data from postman:
{
"username": "testUser",
"email": "bol#example.com",
"first_name": "test",
"last_name": "user",
"password": "test21234",
"role_id":"45"
}
User creation is working fine, Profile is also created. Now I want the role_id: 45 to be saved in the database. that is not happening.

Django - Saving updated information to existing user

I'm creating a multi-step user form for registration. The idea behind it, is when the user registers, it gets their registration information. Then, it saves the users information. In the registration form, if the user selected a certain option (rank = 'Anak'), they are redirected to a base form that obtains the tribe name. What I want it to do, is save the tribe name into that users account that was just created, but I am having trouble doing this since there is no save() function for base forms in Django.
forms.py
class RegisterForm(UserCreationForm):
email = forms.EmailField(
initial='',
required=True,
help_text='Please enter a valid email address'
)
rank = forms.ChoiceField(
label='Are you a PSMC member?',
choices=SavBlock.models.User.rank,
initial=False,
required=True,
help_text='Member accounts will be validated with your HC.',
)
class Meta:
model = User
# username = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
fields = ['username', 'first_name', 'last_name', 'email',
'rank', 'password1', 'password2']
def save(self, commit=True):
user = super(RegisterForm, self).save(commit=False)
user.email = self.cleaned_data['email']
user.ranking = self.cleaned_data['rank']
if commit:
user.save()
return user
class AnakRegisterForm(Form):
tribe = forms.ChoiceField(
label='What tribe are you from, Uce?',
choices=SavBlock.models.Anak.tribe,
initial=False,
required=True,
help_text='Member accounts will be validated with your HC.'
)
class Meta:
model = Anak
fields = ['tribe']
def save(self, commit=True):
user = super(RegisterForm, self).save(commit=False)
user.tribe = self.cleaned_data['tribe']
if commit:
user.save()
return user
views.py
def register(response):
context = {}
# temp_key = {}
if response.method == "POST":
form = RegisterForm(response.POST)
if form.is_valid():
form.save()
if form.cleaned_data.get('rank') == 'Anak':
# temp_key[form.cleaned_data.get('username')] = form.cleaned_data
return redirect('anak_register')
# anak_register(form.cleaned_data)
messages.success(response, 'Registration successful. Please login.')
return redirect('login')
else:
context['register'] = form
else:
form = RegisterForm()
context['register'] = form
return render(request=response, template_name='register/register.html', context={'form': form})
def anak_register(response):
context = {}
if response.method == 'POST':
anak_form = AnakRegisterForm(response.POST)
if anak_form.is_valid():
# TODO: The form obtains the tribe information from the user. Now we must save this information into the
# users account.
"""
anak_form = RegisterForm.save(register)
return anak_form
"""
else:
context['register'] = anak_form
render(request=response, template_name='register/register.html', context={'form': anak_form})
else:
anak_form = AnakRegisterForm(response.POST)
context['anak_register'] = anak_form
# messages.error(request, 'Unsuccessful registration. Invalid information.')
return render(request=response, template_name='register/anak_register.html', context={'form': anak_form})
Before redirected to anak_register, the UserCreationForm saves the users information. The anak_form holds some information that I want associated to the users account that was just created, but like I said, I'm not sure how to do it. There may be an easier way of doing this
models.py
class UserManager(BaseUserManager):
use_in_migrations = True
def _create_user(self, email, password, **extra_fields):
if not email:
raise ValueError('The given email must be set')
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return User
def create_user(self, email, password=None, **extra_fields):
extra_fields.setdefault('is_super', False)
return self._create_user(email, password, **extra_fields)
def create_superuser(self, email, password, **extra_fields):
extra_fields.setdefault('is_superuser', True)
extra_fields.setdefault('is_staff', True)
if extra_fields.get('is_superuser') is not True:
raise ValueError('Superuser must have is_superuser=True')
if extra_fields.get('is_staff') is not True:
raise ValueError('Superuser must have is_staff=True')
return self._create_user(email, password, **extra_fields)
class User(AbstractBaseUser, PermissionsMixin):
rank = [
('Supporter', 'Supporter (non-member)'),
('Anak', 'Anak'),
('Uso', 'Uso'),
('Chief', 'Chief'),
]
tribe = [
('NaKoaHema', 'Na Koa Hema'),
('Alakai', 'Alaka\'i')
]
username = models.CharField("user name", max_length=50, default='', unique=True)
email = models.EmailField("email address", max_length=30, unique=True, blank=True)
first_name = models.CharField("first name", max_length=50)
last_name = models.CharField("last name", max_length=50)
is_active = models.BooleanField('active', default=True)
# password = models.CharField("password", unique=True, max_length=32, default='')
id = models.AutoField(primary_key=True)
is_staff = models.BooleanField('staff status', default=False)
date_joined = models.DateField('date_joined', default=timezone.now)
ranking = models.CharField(choices=rank, max_length=50, default="Supporter")
tribe = models.CharField(choices=tribe, max_length=50, default="None")
objects = UserManager()
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email', 'password'] # 'ranking'
# Magic method returns string of self
def __str__(self):
return f"User {self.first_name} {self.last_name} rank {self.rank}".strip()
#property
def get_full_name(self):
return f"{self.first_name} {self.last_name}".strip()
class Anak(User):
def __init__(self, tribe, *args, **kwargs):
super().__init__(*args, **kwargs)
self.tribe = tribe.title()
self.rank = User.rank[1]
tribe = [
('NaKoaHema', 'Na Koa Hema'),
('Alakai', 'Alaka\'i')
]
I am not 100% sure that correctly understood your intentions, but as I see you redefine Django's user with own class to extend it with additional things (rank, tribe).
The more elegant way to do it (in my opinion) is to create an additional model which will hold specific fields and be connected one-to-one with the standard Django User model. So your models.py will be:
from django.db import models
from django.contrib.auth.models import User
class Anak(models.Model):
rank = [
('Supporter', 'Supporter (non-member)'),
('Anak', 'Anak'),
('Uso', 'Uso'),
('Chief', 'Chief'),
]
tribe = [
('NaKoaHema', 'Na Koa Hema'),
('Alakai', 'Alaka\'i')
]
user = models.OneToOneField(User, on_delete=models.CASCADE) # one Anak instance associated with User instance
tribe = models.CharField(choices=tribe, max_length=50, default="None")
ranking = models.CharField(choices=rank, max_length=50, default="Supporter")
def save(self, *args, **kwargs):
# Create dependent User if not exist
if not self.user.pk:
self.user = User.objects.create_user(username=self.user.username, password=self.user.password)
self.user.is_staff = False
# logic that you need before saving (if needed)
self.tribe = tribe.title()
self.rank = User.rank[1]
self.user.save() # mandatory as create_user is not recognized as save operation
super().save(*args, **kwargs)
Thus, now you may use your forms to create user and Anak associated one-to-one with the user.
Additional tip - is when user already created and logged in - your request.user will contain an object of User model with the related user. So you may use it to create/update related Anak. I.e.:
if response.method == 'POST':
anak = models.Anak.objects.get_or_create(user=response.user) # get Anak if exists, create - otherwise
anak_form = AnakRegisterForm(instance=anak, data=response.POST) # update anak from above
if anak_form.is_valid():
anak_form.save()
Thus, you will use standard Django users (without the need to redefine User by yourself) plus keeping Anak information in a separate table which is created only when needed as an extension to standard User behaviour

Django - How to run consecutive forms?

I have a user registration form that asks for user information and also asks a question: "Are you a PSMC member?"
The options are:
rank = [
('Supporter', 'Supporter (non-member)'),
('Anak', 'Anak'),
('Uso', 'Uso'),
('Chief', 'Chief'),
]
If Supporter is selected, then the registration form proceeds and saves user info, etc. This part works fine. However, if Anak is selected, I want it to take the user to another form that asks additional questions.
In my forms.py, I have class RegisterForm which is the main registration form for all users. I also have class AnakRegisterForm which is what I want it to continue on to. I used Django's AuthenticationForm based off what I read from their website (but I could be wrong). I know the issue is in views.py register function. Specifically:
if rank == 'Anak':
anak_register(response)
During my debug session, after it moves response to anak_register function, it gets a bunch of scrambled information. I'm pretty lost, any help would be appreciated. Here is my code:
forms.py
class RegisterForm(UserCreationForm):
email = forms.EmailField(
initial='',
required=True,
help_text='Please enter a valid email address'
)
rank = forms.ChoiceField(
label='Are you a PSMC member?',
choices=SavBlock.models.User.rank,
initial=False,
required=True,
help_text='Member accounts will be validated with your HC.'
)
class Meta:
model = User
# username = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
fields = ['username', 'first_name', 'last_name', 'email',
'rank', 'password1', 'password2']
def save(self, commit=True):
user = super(RegisterForm, self).save(commit=False)
user.email = self.cleaned_data['email']
user.ranking = self.cleaned_data['rank']
if commit:
user.save()
return user
class AnakRegisterForm(AuthenticationForm):
tribe = forms.ChoiceField(
label='What tribe are you from, Uce?',
choices=SavBlock.models.Anak.tribe,
initial=False,
required=True,
help_text='Member accounts will be validated with your HC.'
)
class Meta:
model = Anak
fields = ['tribe']
def save(self, commit=True):
user = super(AnakRegisterForm, self).save(commit=False)
user.tribe = self.cleaned_data['tribe']
if commit:
user.save()
return user
class UsoRegisterForm(AuthenticationForm):
pass
class ChiefRegisterForm(AuthenticationForm):
pass
views.py
def register(response):
context = {}
if response.method == "POST":
form = RegisterForm(response.POST)
if form.is_valid():
form.save()
rank = form.cleaned_data.get('rank')
if rank == 'Anak':
anak_register(response)
else:
form.save()
messages.success(response, 'Registration successful. Please login.')
return redirect('login')
else:
context['register'] = form
else:
form = RegisterForm()
context['register'] = form
return render(request=response, template_name='register/register.html', context={'form': form})
def anak_register(response):
# context = {}
if response.method == "POST":
form = AnakRegisterForm(response.POST)
if form.request.is_valid():
form.save()
messages.success(response, 'Registration successful. Please login.')
return redirect('login')
else:
'''
context['register'] = form
'''
else:
form = AnakRegisterForm()
# context['register'] = form
# messages.error(request, 'Unsuccessful registration. Invalid information.')
# form = RegisterForm
return render(request=response, template_name='register/register.html', context={'form': form})
models.py
class User(AbstractBaseUser, PermissionsMixin):
rank = [
('Supporter', 'Supporter (non-member)'),
('Anak', 'Anak'),
('Uso', 'Uso'),
('Chief', 'Chief'),
]
tribe = [
('NaKoaHema', 'Na Koa Hema'),
('Alakai', 'Alaka\'i')
]
username = models.CharField("user name", max_length=50, default='', unique=True)
email = models.EmailField("email address", max_length=30, unique=True, blank=True)
first_name = models.CharField("first name", max_length=50)
last_name = models.CharField("last name", max_length=50)
is_active = models.BooleanField('active', default=True)
# password = models.CharField("password", unique=True, max_length=32, default='')
id = models.AutoField(primary_key=True)
is_staff = models.BooleanField('staff status', default=False)
date_joined = models.DateField('date_joined', default=timezone.now)
ranking = models.CharField(choices=rank, max_length=50, default="Supporter")
objects = UserManager()
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email', 'password', 'ranking']
# Magic method returns string of self
def __str__(self):
return f"User {self.first_name} {self.last_name} rank {self.rank}".strip()
#property
def get_full_name(self):
return f"{self.first_name} {self.last_name}".strip()
class Anak(User):
def __init__(self, first_name, last_name, tribe):
super().__init__(first_name, last_name)
self.tribe = tribe.title()
self.rank = User.rank[1]
EDIT
I changed AuthenticationForm to UserCreationForm and now it accepts the form. However, when I try to run it, I get the following error:
TypeError at /register/
__init__() missing 1 required positional argument: 'tribe'
If someone could point me in the right direction, I'd appreciate it!
Not sure how I missed this, but the best solution that I found is to implement Django Form Wizard. More information can be found here:
https://django-formtools.readthedocs.io/en/latest/wizard.html

Django Rest Framework: Issue with extended User model and serialization

I' extending the default Django user model to make a customised user profile with additional fields.The following are the related components.
models.py
class CandidateProfile(models.Model):
user = models.OneToOneField(
User, on_delete=models.CASCADE, related_name="user")
exp = models.IntegerField(null=True, blank=True)
serilaizers.py
class CandidateProfileSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(source='pk', read_only=True)
username = serializers.CharField(source='user.username')
email = serializers.CharField(source='user.email')
groups = serializers.RelatedField(read_only=True)
password = serializers.CharField(max_length=128, source='user.password,read_only=True')
class Meta:
model = CandidateProfile
fields = ('id', 'username', 'password', 'email', 'groups')
depth = 1
def update(self, instance, validated_data):
print("In Update" + '*' * 50)
user = User.objects.get(pk=instance.user.pk)
user = instance.user
user.email = validated_data.get('user.email', user.email)
user.first_name = validated_data.get('user.first_name',
user.first_name)
user.last_name = validated_data.get('user.last_name', user.last_name)
user.save()
instance.gender = validated_data.get('gender', instance.gender)
instance.save()
return instance
def create(self, validated_data):
print('*' * 100)
print(validated_data)
user_data = validated_data.pop('user')
print(user_data)
user = User.objects.create_user(**user_data)
g = Group.objects.get(name="Candidate")
g.user_set.add(user)
user.save()
print(validated_data)
print('*' * 100)
profile = CandidateProfile.objects.create(user=user, **validated_data)
return user
views.py
class CandidateRegister(APIView):
def get(self, request, format=None):
candidate_list = User.objects.filter(groups=Group.objects.get(
name="Candidate"))
serializer = CandidateProfileSerializer(candidate_list, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = CandidateProfileSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I've succcessfully created the user profile as well as the extended Candidate profile.But i'm encoutering an error on doing the same as follows :
Got AttributeError when attempting to get a value for field `username` on serializer `CandidateProfileSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `User` instance.
Original exception text was: 'CandidateProfile' object has no attribute 'username'.
Even with this execpiton the User profile and the related Candidate profile is created.
You can use the SerializerMethodField from docs like -
class CandidateProfileSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(source='pk', read_only=True)
username = serializers.SerializerMethodField()
def get_username(self, obj):
return obj.user.username

Categories