How to prevent current user from rating himself using Django model constraints - django

So I have a user model and each user should be able to rate other users but shouldn't be able to rate themselves.
Model for rating is
#Rating field in User model
ratings = models.ManyToManyField('self', through='Rating',
symmetrical=False,
related_name='rated_by')
#Rating model
class Rating(models.Model):
rating = models.IntegerField(validators=[validate_rating])
from_user = models.ForeignKey(User, related_name="from_people", on_delete=models.CASCADE)
to_user = models.ForeignKey(User, related_name="to_people", on_delete=models.CASCADE)
Is there a way I can make this functionality? I was thinking django model constraints will work but I do not know how to go about it. How do I filter a request to grab the specific user to prevent them from rating themselves?
I tried:
class Rating(models.Model):
rating = models.IntegerField(validators=[validate_rating])
from_user = models.ForeignKey(User, related_name="from_people",
on_delete=models.CASCADE)
to_user = models.ForeignKey(User, related_name="to_people",
on_delete=models.CASCADE)
class Meta:
constraints = [
CheckConstraint(
check = Q(from_user != User,
to_user != User),
name = 'check_user',
)
]
but I get a Metacheck = Q(from_user != User, NameError: name 'from_user' is not defined as an error

You can add a database level check constraint to your model that will prevent the Rating model from having a record with the from_user and to_user the same.
from django.db.models import Q, F, CheckConstraint
class Rating(models.Model):
rating = models.IntegerField(validators=[validate_rating])
from_user = models.ForeignKey(User, related_name="from_people", on_delete=models.CASCADE)
to_user = models.ForeignKey(User, related_name="to_people", on_delete=models.CASCADE)
class Meta:
constraints = [
CheckConstraint(check=~Q(from_user=F('to_user')), name='no_self_rating')
]
Django docs constraint reference.
The above adds the constraint at the database level, but does not do any form validation.
You also ask
How do I filter a request to grab the specific user to prevent them from rating themselves?
It's not clear what you mean here. The request object passed to the view function has a user attribute that is the user using the application. I'm not sure what your view looks like but:
def my_view(request):
request.user # This is the user making the action
... # Other view code
return HttpResponse(...)
You can pass request.user to a form and validate the user is not rating themselves.

A negative CheckConstraint should do what you want.
from django.db import models
class Rating(models.Model):
rating = models.IntegerField(validators=[validate_rating])
from_user = models.ForeignKey(User, related_name="from_people", on_delete=models.CASCADE)
to_user = models.ForeignKey(User, related_name="to_people", on_delete=models.CASCADE)
class Meta:
constraints = [
models.CheckConstraint(
check=~models.Q(from_user=models.F('to_user')),
name='users_cannot_rate_themselves'
),
]

Related

how to filter a model in a listview

I have a page that displays a list of interviewers and some important info about each user.
One of the things that I want it to show is the number of users who have been interviewed by that specific interviewer.
I wrote a view like this:
class ManagerUsers(ListView):
model = User
template_name = 'reg/manager-users.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['scientific_interviewers'] = User.objects.filter(role='theory_interviewer').all()
context['interviewed_number'] = len(ScientificInfo.objects.filter(user__role='applicant', is_approved=True, interviewer=?????))
the interviewer field should be equal to that object's user but I don't know what to do exactly.
the output should be something like this:
object 1 : user's name, user's other info, user's interviewed_number
....
these are my models:
USER_ROLE_CHOICES = (('0', 'applicant'),
('1', 'theory_interviewer'),)
class User(AbstractUser):
id = models.AutoField(primary_key=True)
role = models.CharField(max_length=25, null=True, choices=USER_ROLE_CHOICES, default=USER_ROLE_CHOICES[0][0])
username = models.CharField(unique=True, max_length=13)
first_name = models.CharField(max_length=32, null=True, default=None)
last_name = models.CharField(max_length=64, null=True, default=None)
class ScientificInfo(models.Model):
id = models.AutoField(primary_key=True)
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='user')
interviewer = models.OneToOneField(User, on_delete=models.CASCADE, related_name='interviewer')
is_approved = boolean field
You can override the .get_queryset() method [Django-doc] to return only the interviewers. By using .annotate(…) [Django-doc] you can add an extra attribute to these Users:
from django.db.models import Count
class ManagerUsersView(ListView):
model = User
context_object_name = 'scientific_interviewers'
template_name = 'reg/manager-users.html'
def get_queryset(self):
return super().get_querset().filter(
role='1'
).annotate(
interviewed_number=Count('interviewer', filter=Q(interviewer__user__role='0', interviewer__is_approved=True))
)
The Users that arise from this queryset will have an extra attribute .interviewed_number with the number of approved ScientificInfos where that user was the interviewer.
Note: In Django, class-based views (CBV) often have a …View suffix, to avoid a clash with the model names.
Therefore you might consider renaming the view class to ManagerUsersView, instead of ManagerUsers.
Note: The related_name=… parameter [Django-doc]
is the name of the relation in reverse, so from the User model to the ScientificInfo
model in this case. Therefore it (often) makes not much sense to name it the
same as the forward relation. You thus might want to consider renaming the interviewer relation to interviews.

ValueError at /spotify/liked/: Cannot assign 'some value': "Liked_Songs.track_name" must be a "Profile" instance

So I have users whose profiles are automatically created after user creation with the help of Django Signals
models.py
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
class Meta:
db_table = "Profile"
class Liked_Songs(models.Model):
track_name = models.ForeignKey(Profile , on_delete= models.CASCADE , related_name="track")
artiste_name= models.ForeignKey(Profile, on_delete= models.CASCADE , related_name="artiste")
album_name = models.ForeignKey(Profile, on_delete= models.CASCADE, related_name= "album")
class Meta:
db_table = "Liked Songs"
def __str__(self):
return self.track_name
In this Liked_Songs model, my views.py accesses an API and my aim is to allow all fields in that model to be populated with data from the API. So there will be multiple track_name etc received. So each Profile can have many track names etc. Is the ForeignKey appropriate for this?
However, when I use this route below, i get an error I have stated in the problem description.
Views.py
def liked(request):
try:
if "access_token" in request.session:
sp = Spotify(auth = request.session.get("access_token"))
liked = sp.current_user_saved_tracks(limit=30)['items']
for idx, item in enumerate(liked):
track = item['track']["name"]
artist= item["track"]["artists"][0]["name"]
album = item["track"]["album"]["name"]
Liked_Songs.objects.create(
track_name= track,
artiste_name= artist,
album_name = album
).save()
except SpotifyException:
return redirect(reverse("login"))
you are storing profile id in track so you cannot pass name in it so try to pass profile id in it
try this track_name_id = profile_id

Django model order the queryset based on booleanfield True/False that related with User FK profile

I have two django models as follows:
The first one is a user profile, which has a FK to User model:
class Profile(models.Model):
PRF_user = models.OneToOneField(User, related_name='related_PRF_user', on_delete=models.CASCADE)
PRF_Priority_Support = models.BooleanField(default=False)
and the second is ticket model which has a FK to User model:
class ticket(models.Model):
ticket_status_options = [
('open', 'open'),
('wait_customer_reply', 'wait_customer_reply'),
('replied_by_staff', 'replied_by_staff'),
('replied_by_customer', 'replied_by_customer'),
('solved', 'solved'),
]
TKT_USER = models.ForeignKey(User, related_name='TKT_USER', on_delete=models.CASCADE)
TKT_DEB = models.ForeignKey('Site_departments', related_name='related_ticket_department', on_delete=models.CASCADE)
TKT_SUB = models.CharField(max_length=50, db_index=True, verbose_name="ticket subject")
TKT_BOD = models.TextField(verbose_name="ticket body")
TKT_image_attachment = models.ImageField(upload_to='TKT_img_attachment', blank=True, null=True , default=None)
TKT_CREATED_DATE = models.DateTimeField(auto_now_add=True)
TKT_UPDATED_DATE = models.DateTimeField(auto_now=True)
I want to sort the tickets based on user profile Priority_Support:
If the user profile PRF_Priority_Support is True, I want to sort it first inside my views QuerySet, otherwise (if PRF_Priority_Support is False) I want to sort it normally.
How can I do this?
You should name your model starting with a capital letter.
And for ordering the tickets, you can use something like this:
' queryset_list = ticket.objects.order_by('-TKT_USER__related_PRF_user__PRF_Priority_Support')
In filtering, when you want to span relationships, you use double underscore __ .
More on this here:
https://docs.djangoproject.com/en/3.1/topics/db/queries/#lookups-that-span-relationships
Another way is adding ordering to your model's Meta class.
For Example:
MyModel(models.Model):
class Meta:
ordering = ('-my_boolean_field ',)
Hi you should filter as follow:
Model.objects.filter(field=True) or False depending on what you need
Regards

Django models: Unique values for foreign keys

I am building an Instagram like app and trying to make a like model. Each user can like a post however, it should not be possible to like the same post twice.
class Like(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
This is my model however, I am able to create 2 identical objects. For example user 1 can have 2 like objects like to post 1.
Is there a way to do this?
Yes, you can mark the combination of the user and post field as unique with a UniqueConstraint [Django-doc]:
class Like(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
class Meta:
constraints = [
models.UniqueConstraint(fields=['user', 'post'], name='like_once')
]
Prior to django-2.2, you can make use of the unique_together option [Django-doc]:
class Like(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = [['user', 'post']]
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.

Remove option from generic class view in Django

I just started learning Django this week and I'm trying to figure out how I can remove an option from a select menu being rendered in a class based view. The dropdown is for a Foreign Key field that links to my users table.
The functionality here is that I do not want the current user logged into show up on that list (basically I don't want someone to be able to select themself). How can I go about doing this?
View:
class TransferCreateView(CreateView):
model = Transfer
template_name = 'points/transfer_form.html'
fields = ['receiver', 'message', 'amount']
Model:
class Transfer(models.Model):
receiver = models.ForeignKey(User, null=False,
on_delete=models.CASCADE, related_name='receiver')
sender = models.ForeignKey(User, null=False, on_delete=models.CASCADE, related_name='sender')
amount = models.IntegerField(
validators=[
MinValueValidator(1),
MaxValueValidator(1000)],
null=False)
message = models.CharField(max_length=100)
date_sent = models.DateTimeField(default=timezone.now)
Basically, I don't want the person who is the sender (which i was going to set in the code in a form_valid() function) to be an option for 'receiver' in the template when it renders.
Override the get_form method of the CreateView and change the queryset of that field, something like this:
class TransferCreateView(CreateView):
model = Transfer
template_name = 'points/transfer_form.html'
fields = ['receiver', 'message', 'amount']
def get_form(self, form_class):
form = super().get_form(form_class)
form.fields['receiver'].queryset = User.objects.exclude(id=self.request.user.id)
return form