How to run two methods inside django models in parallel manner? - django

I'm incrementing two different fields in User models which are the number of likes given to user's post and number of likes received by the user's post. I did this through adding two methods in class model. It is working when I'm liking other posts but however, if I liked my own post it is not working. Anyone know how to solve this problem?
Code snippet for user models
class User(models.Model):
username = models.CharField(max_length=100)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
email = models.EmailField(null=True)
company = models.ForeignKey(Company, on_delete=models.CASCADE)
datetime_joined = models.DateTimeField(auto_now_add=True)
num_likes_received = models.IntegerField(default=0)
num_dislikes_received = models.IntegerField(default=0)
num_likes_given = models.IntegerField(default=0)
num_dislikes_given = models.IntegerField(default=0)
total_votes = models.IntegerField(default=0)
prediction = models.BooleanField(default=True)
def add_total_votes(self, vote):
self.total_votes += vote
self.save()
def inc_num_likes_received(self):
self.num_likes_received += 1
print("1 like received")
self.save()
def inc_num_likes_given(self):
self.num_likes_given += 1
print("1 like given")
self.save()
Code snippet for Post Model
class Post(models.Model):
post = models.TextField()
num_likes = models.IntegerField(default=0)
num_dislikes = models.IntegerField(default=0)
username = models.ForeignKey(User, on_delete=models.CASCADE)
datetime_comments = models.DateTimeField(auto_now_add=True)
def add_num_likes(self):
self.num_likes+=1
self.save()
def __str__(self):
return str(self.id)
Function for submitting like to certain post and at the same time this is where I executed the created two method from the models. (inc_num_likes_received and inc_num_likes_given)
def submit_like(request):
User_PK = User.objects.get(username=request.user)
User_Give_Like = get_object_or_404(User, pk=User_PK.id)
pk = request.POST.get('Status', None)
post = get_object_or_404(Post, pk=pk)
post.add_num_likes()
User_PK_Receive = User.objects.get(id=post.username.id)
User_Receive_Like = get_object_or_404(User, pk=User_PK_Receive.id)
LikeObject = Likes(username = User_Give_Like, post = post, liked=True, disliked=False)
LikeObject.save()
User_Give_Like.inc_num_likes_given()
User_Receive_Like.inc_num_likes_received()
return HttpResponse(200)
To become more specific these two lines of code below are the methods. The problem here is that the first line of code are not actually working but the last line are the only working. Whenever I rearranged these two lines of code the only executed was the last line of code.
....
User_Give_Like.inc_num_likes_given()
User_Receive_Like.inc_num_likes_received()
Example:
In this case, User_Receive_Like.inc_num_likes_given() are working but User_Receive_Like.inc_num_likes_received() are not working and vice-versa.
....
User_Receive_Like.inc_num_likes_received()
User_Give_Like.inc_num_likes_given()
The expected output is both fields num_likes_given and num_likes_received must incremented by 1.

Related

Django rest framework Detail not found while deleting

i have a a problem deleting an object via API. So i have 2 models, Answer and Vote vote is connected to Answer via foreignKey like this
class Vote(models.Model):
class AnswerScore(models.IntegerChoices):
add = 1
subtract = -1
score = models.IntegerField(choices=AnswerScore.choices)
answer = models.ForeignKey('Answer', on_delete=models.PROTECT)
user = models.ForeignKey('users.CustomUser', on_delete=models.PROTECT)
class Meta:
unique_together = ('answer', 'user',)
class Answer(models.Model):
answer = models.TextField()
created_at = models.DateTimeField(editable=False, default=timezone.now)
updated_at = models.DateTimeField(default=timezone.now)
user = models.ForeignKey('users.CustomUser', on_delete=models.PROTECT)
question = models.ForeignKey('Question', on_delete=models.PROTECT)
number_of_points = models.IntegerField(default=0)
moderate_status = models.BooleanField(default=False)
Now i have an API endpoint for creating a Vote for user on a particular answer
path('answers/<int:pk>/vote', VoteCreate.as_view()),
Looks like this:
class VoteSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault())
answer = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Vote
fields = '__all__'
class VoteCreate(ParentKeyAPIView, generics.CreateAPIView):
model = Vote
parent_model_field = 'answer_id'
serializer_class = VoteSerializer
def perform_create_kwargs(self, **kwargs):
answer_id = self.kwargs['pk']
answer = Answer.objects.get(id=int(answer_id))
if Vote.objects.filter(user=self.request.user, answer=answer).exists():
old_vote = Vote.objects.get(user=self.request.user, answer=answer)
answer -= old_vote.score
answer.save()
old_vote.delete()
return super().perform_create_kwargs(user=self.request.user, answer_id=answer_id, **kwargs)
Im using the same serializer just like above*
I get an error:
Not Found: /api/v1/answers/55/voteDelete
There is no any traceback, i have checked in fact that in database the Vote with answer_id 55 exists and i can delete them via django-admin but cant via API.
Do you have any idea why?
First thing - let's change get_queryset to get_object, because you want single object. Then instead of answer_id=self.kwargs['pk'] simplify it to answer=self.kwargs['pk'].
Another thing - I think it's might be better to delete old vote while creating new one. You don't need serializer or special view for that. Just add to function creating new Vote object in your view:
class VoteCreate(...):
...
def perform_create_kwargs(self, **kwargs):
answer_id = self.kwargs['pk']
answer = Answer.object.get(id=int(answer_id))
if Vote.objects.filter(user=self.request.user, answer=answer).exists():
old_vote = Vote.objects.get(user=self.request.user, answer=answer)
answer.number_of_points -= old_vote.score
answer.save()
old_vote.delete()
return super().perform_create_kwargs(user=self.request.user, answer_id=answer_id, **kwargs)

How to render a foreign key field in Django

Desired outcome: When I render a Poller and its associated comments
I would like to also render the Vote a user selected for the Poller along with his comment (Note: A user can only comment if he voted on that poller).
Side note: A user can make one vote to a Poller and post one comment to a Poller. He can only comment if he voted beforehand.
# Models
class Poller(models.Model):
poller_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
created_on = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(Account, on_delete=models.CASCADE)
poller_text = models.CharField(max_length=333)
poller_choice_one = models.CharField(max_length=20)
poller_choice_two = models.CharField(max_length=20)
class Vote(models.Model):
poller = models.ForeignKey(Poller, on_delete=models.CASCADE, related_name='vote')
user = models.ForeignKey(Account, on_delete=models.CASCADE)
created_on = models.DateTimeField(auto_now_add=True)
poller_choice_one_vote = models.BooleanField(default=False)
poller_choice_two_vote = models.BooleanField(default=False)
def __str__(self):
return f'Vote by {self.user}'
class Comment(models.Model):
poller = models.ForeignKey(Poller, on_delete=models.CASCADE, related_name='PollerComment')
user = models.ForeignKey(Account, on_delete=models.CASCADE)
created_on = models.DateTimeField(auto_now_add=True)
comment = models.TextField(max_length=350)
flag_count = models.IntegerField(default=0)
upvote_count = models.IntegerField(default=0)
downvote_count = models.IntegerField(default=0)
# View
#require_GET
def render_single_poller(request, poller_id):
# Retrieve comments associated to the Poller
comments_qs = PollerComment.objects.filter(poller_id=poller_id)
context = {
'comments_qs': comments_qs,
}
return render(request, 'pollboard/single_poller.html', context)
I tried to do it via a template filter like so:
# pollboard_tags.py
#register.filter(name='get_vote')
def get_voted(self):
self.vote_made = 'Test'
print(self.vote.poller_choice_one_vote)
if self.vote.poller_choice_one_vote:
self.vote_made = 'One'
else:
self.vote_made = 'Two'
return self.vote_made
# template
<div class="commentors-poller-choice">{{ comment|get_vote }}</div>
throws
RelatedObjectDoesNotExist at /poller/68c725eb-277e-4b5b-a61b-b4a02bf5e854/
PollerComment has no vote.
I fear that I'm already overcomplicating things here. I hope there is a more straightforward solution to this like idk expanding the comments queryset by the required information for rendering?
If a user can vote on a poller only once, you can filter with:
#register.filter(name='get_vote')
def get_voted(self):
vote = Vote.objects.get(poller=self.poller, user=self.user)
return 'One' if vote.poller_choice_one_vote else 'Two'

Getting 400_BAD_REQUEST when i'm trying to return data from django endpoint

Currently i'm trying to make an APIView that returns data like this:
["question_text": "answer_text", "question_text": "answer_text"] ...
So, basically i want to make that in order to get the current user quizzes attempts, with the next REQUEST BODY:
"quizname"
my UserAnswer model looks like this:
class UserAnswers(models.Model):
user = models.ForeignKey(User, related_name='User', on_delete=models.CASCADE)
answer = models.ForeignKey(Answer,related_name='Answer', on_delete=models.DO_NOTHING)
Answer model :
class Answer(models.Model):
question = models.ForeignKey(Question, related_name='answer', on_delete=models.CASCADE)
answer_text = models.CharField(max_length=255, default='', verbose_name="Answer")
is_right = models.BooleanField(default=False)
is_checked = models.BooleanField(default=False)
def __str__(self):
return self.answer_text
Question model:
class Question(models.Model):
quiz = models.ForeignKey(Quiz, related_name='question', on_delete=models.CASCADE)
question_text = models.CharField(max_length=255, default='', verbose_name="Question")
def __str__(self):
return self.question_text
Basically, the UserAnswers table contains a FK to Answer, that contains a FK to Question, that contains a FK to quiz
I tried to do this in my APIView:
class UserAnswersView(APIView):
permission_classes = [IsAuthenticated]
def get(self,request):
try:
data = request.data # quizName
toReturn = {}
userAnswers = UserAnswers.objects.filter(user=request.user) # Getting all entries from UserAnswers table WHERE user = request.user
for userEntry in userAnswers: # parsing al entries
answer = Answer.objects.get(answer_text=userEntry.answer) # Getting the answer of every entry
question = Question.objects.get(id=answer.question_id) # Getting the question of every entry
quiz = Quiz.objects.get(id=question.quiz_id) # Getting the quiz of every entry
if (quiz.category == request.data): # Checking if the current quiz is the one that we have interests in (the quiz name that is given by the request body)
toReturn[question.question_text] = {}
toReturn[question.question_text] = answer.answer_text
return Response(toReturn,status=status.HTTP_200_OK)
except Exception as e:
return Response(status=status.HTTP_400_BAD_REQUEST)
But i'm getting
Bad Request: /answers/
"GET /answers/ HTTP/1.1" 400 0
Does anyone know how am i supposed to return data in the format i want?
Apparently, answer = Answer.objects.get(answer_text=userEntry.answer) returned 3 answers instead of 1 because i filtered using the answer_text and there are multiple answers with the same text.
So i changed to answer = Answer.objects.get(id=userEntry.answer_id) and now everything works properly.

Passing parent object into CreateView for a child object

I'm creating a dashboard to edit a tour app.
Per tour I have a child record in which I define steps. The 2 models look like this:
models.py
class Tour(models.Model):
tour_id = models.CharField(primary_key=True,unique=True, max_length=10)
country = models.ForeignKey(Countries, models.DO_NOTHING, db_column='country')
language = models.ForeignKey(Language, models.DO_NOTHING, db_column='language')
lastupddtm = models.DateTimeField(default=timezone.now)
productid = models.CharField(max_length=50)
title = models.CharField(max_length=50)
description = models.CharField(max_length=100)
descrlong = models.CharField(max_length=1000)
live = models.CharField(max_length=1)
image = models.ImageField(upload_to=upload_tour_image, storage=OverwriteStorage(), blank=True, null=True)
class Meta:
db_table = 'tour'
verbose_name_plural = "tour"
def get_language_flag(self):
return self.language.flag.url
def __str__(self):
return str(self.tour_id) + ' - ' + str(self.title) + ' - ' + str(self.description)
class Toursteps(models.Model):
# tour_id = models.OneToOneField(Tour, models.DO_NOTHING, db_column='tour_id')
tour = models.ForeignKey(Tour, related_name='toursteps', on_delete=models.CASCADE)
step = models.IntegerField(unique=True)
title = models.CharField(max_length=50)
description = models.CharField(max_length=100)
descrlong = models.CharField(max_length=1000)
audiotext = models.TextField()
latitude = models.FloatField()
longitude = models.FloatField()
radius = models.FloatField()
image = models.ImageField(upload_to=upload_tour_step_image, blank=True, null=True)
class Meta:
db_table = 'tourSteps'
verbose_name_plural = "tourSteps"
def __str__(self):
return str(self.tour) + "|" + str(self.step)
After I created a Tour, I go to a detail page. From there I can click a link to add a step for this tour.
This is where the problem is. I pass the tour_id as a variable into the url, but I can't find a way to pick it up in the CreateView of the step.
urls.py
urlpatterns = [
path('tour/<str:pk>/detail', views.TourDetailView.as_view(), name='tour_detail'),
path('tour/<str:pk>/edit', views.UpdateTourView.as_view(), name='tour_edit'),
path('tour/<str:pk>/remove', views.DeleteTourView.as_view(), name='tour_remove'),
path('tour/<str:tour_id>/step/new', views.CreateTourStepView.as_view(), name='tour_step_new')
]
Tour detail view
<p><span class="glyphicon glyphicon-plus"></span></p>
views.py
class CreateTourStepView(LoginRequiredMixin,CreateView):
login_url = '/login/'
redirect_field_name = 'tour_admin/tour_list.html'
success_url = '/'
form_class = TourStepForm
model = Toursteps
def get_context_data(self, **kwargs):
context = super(CreateTourStepView, self).get_context_data(**kwargs)
print(context['tour_id'])
return context
forms.py
class TourStepForm(forms.ModelForm):
class Meta():
model = Toursteps
#fields = '__all__'
exclude = ('tour',)
def form_valid(self, form):
if form.is_valid():
form.instance.tour_id = self.request.GET("tour_id")
form.instance.save()
return HttpResponseRedirect(self.get_success_url())
def get_success_url(self):
return reverse('tour_detail', kwargs={'pk':form.instance.tour_id})
First, your form_valid() and get_success_url() methods belong in your view, not in your form.
Second, the tour_id is passed to the view's kwargs, it's not a query parameter, hence not in self.request.GET. You can find it in self.kwargs.
Third, you need to actually fetch the Tour from your database, not just assign the tour_id. I could post to any tour_id if I wanted and there's no guarantee the tour_id belongs to an actual Tour object. Return a 404 if the tour doesn't exist. And if it exists, assign it to the tour step.
Finally, you should not assign to and save form.instance. You should get the instance using step = form.save(commit=False), then assign to step and save step.

Object-level permissions in Django

I have a ListView as follows, enabling me to loop over two models (Market and ScenarioMarket) in a template:
class MarketListView(LoginRequiredMixin, ListView):
context_object_name = 'market_list'
template_name = 'market_list.html'
queryset = Market.objects.all()
login_url = 'login'
def get_context_data(self, **kwargs):
context = super(MarketListView, self).get_context_data(**kwargs)
context['scenariomarkets'] = ScenarioMarket.objects.all()
context['markets'] = self.queryset
return context
The two market models are as follows:
class Market(models.Model):
title = models.CharField(max_length=50, default="")
current_price = models.DecimalField(max_digits=5, decimal_places=2, default=0.50)
description = models.TextField(default="")
shares_yes = models.IntegerField(default=0)
shares_no = models.IntegerField(default=0)
b = models.IntegerField(default=100)
cost_function = models.IntegerField(default=0)
open = models.BooleanField(default=True)
def __str__(self):
return self.title[:50]
def get_absolute_url(self):
return reverse('market_detail', args=[str(self.id)])
class ScenarioMarket(models.Model):
title = models.CharField(max_length=50, default="")
description = models.TextField(default="")
b = models.IntegerField(default=100)
cost_function = models.IntegerField(default=0)
most_likely = models.CharField(max_length=50, default="Not defined")
open = models.BooleanField(default=True)
def __str__(self):
return self.title[:50]
def get_absolute_url(self):
return reverse('scenario_market_detail', args=[str(self.id)])
And my user model is as follows:
class CustomUser(AbstractUser):
points = models.DecimalField(
max_digits=20,
decimal_places=2,
default=Decimal('1000.00'),
verbose_name='User points'
)
bets_placed = models.IntegerField(
default=0,
verbose_name='Bets placed'
)
net_gain = models.DecimalField(
max_digits=20,
decimal_places=2,
default=Decimal('0.00'),
verbose_name='Net gain'
)
class Meta:
ordering = ['-net_gain']
What I want happen is that different users see different sets of markets. For example, I want users from company X to only see markets pertaining to X, and same for company Y, Z, and so forth.
Four possibilities so far, and their problems:
I could hardcode this: If each user has a company feature (in addition to username, etc.), I could add a company feature to each market as well, and then use if tags in the template to ensure that the right users see the right markets. Problem: Ideally I'd want to do this through the Admin app: whenever a new market is created there, it would be specified what company can see it.
I could try to use Django's default permissions, which of course would be integrated with Admin. Problem: Setting a view permission (e.g., here) would concern the entire model, not particular instances of it.
From googling around, it seems that something like django-guardian might be what I ultimately have to go with. Problem: As I'm using a CustomUser model, it seems I might run into problems there (see here).
I came across this here on SO, which would enable me to do this without relying on django-guardian. Problem: I'm not clear on how to integrate that into the Admin app, in the manner that django-guardian seems able to.
If anyone has any advice, that would be greatly appreciated!
You can add some relationships between the models:
class Company(models.Model):
market = models.ForeignKey('Market', on_delete=models.CASCADE)
...
class CustomUser(AbstractUser):
company = models.ForeignKey('Company', on_delete=models.CASCADE)
...
then in your view you can simply filter the queryset as appropriate:
class MarketListView(LoginRequiredMixin, ListView):
context_object_name = 'market_list'
template_name = 'market_list.html'
login_url = 'login'
def get_queryset(self):
return Market.objects.filter(company__user=self.request.user)
Note, you don't need the context['markets'] = self.queryset line in your get_context_data; the queryset is already available as market_list, since that's what you set the context_object_name to.