I'm new to programming and I'm practicing on making a poll app. But im stuck trying to get the percentage of each choice voted, like so:
Models.py:
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
def __str__(self):
return self.question_text
def was_published_recently(self):
now = timezone.now()
return now - datetime.timedelta(days=1) <= self.pub_date <= now
was_published_recently.admin_order_field = 'pub_date'
was_published_recently.boolean = True
was_published_recently.short_description = 'Published recently?'
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __str__(self):
return self.choice_text
Views.py:
class IndexView(ListView):
template_name = 'posts/index.html'
context_object_name = 'latest_question_list'
def get_queryset(self):
"""
Return the last five published questions (not including those set to be
published in the future).
"""
return Question.objects.filter(pub_date__lte=timezone.now()).order_by('-pub_date')[:5]
class DetailView(DetailView):
model = Question
template_name = 'posts/detail.html'
class ResultsView(DetailView):
model = Question
template_name = 'posts/results.html'
def get_context_data(self, *args, **kwargs):
context = super(ResultsView, self).get_context_data(*args, **kwargs)
q = Question.objects.get(pk=self.kwargs['pk'])
total = q.choice_set.aggregate(Sum('votes'))
percentage = q.choice_set.get(
pk=self.kwargs.get('pk')).votes / total['votes__sum']
context['total'] = total['votes__sum']
context['percentage'] = percentage
return context
def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
try:
selected_choice = question.choice_set.get(pk=request.POST['choice'])
except (KeyError, Choice.DoesNotExist):
# Redisplay the question voting form.
return render(request, 'posts/detail.html', {
'question': question,
'error_message': "You didn't select a choice.",
})
else:
selected_choice.votes += 1
selected_choice.save()
# Always return an HttpResponseRedirect after successfully dealing
# with POST data. This prevents data from being posted twice if a
# user hits the Back button.
return HttpResponseRedirect(reverse('posts:results', args=(question.id,)))
P
The Problem is that, as I try to get the percentage, im getting the wronk pk, and dont know how to make it right. In this case, im trying to get each choice votes and divide by the total votes, the total works just fine, but cant get the value for each choice.
Any tips? is there a easier way of doing this?
You can use annotate to calculate the percentage for each Choice. The number of votes is an integer so you'll need to Cast it to a float so that you use float division rather than integer division
from django.db.models.functions import Cast
from django.db.models import Sum, FloatField
question = Question.objects.get(pk=self.kwargs['pk'])
total_votes = question.choice_set.aggregate(Sum('votes'))['votes__sum']
choices = question.choice_set.annotate(
percentage=Cast('votes', output_field=FloatField()) / total_votes
)
for choice in choices:
print(choice, choice.percentage)
Related
Here is the case, I need the last records of the model to be displayed on the page, for this I added a new pub_date record in the models to add to the queue of records, I also added this to views.py, and it kind of displays, but both records.
views.py code
class IndexView(generic.ListView):
template_name = 'Homepage/index.html'
model = Goods
context_object_name = 'goods'
def description(self):
return self.description_text
def price(self):
return self.price_text
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
numbers = Number.objects.all()
context['numbers'] = numbers
return context
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
avaibilitys = Avaibility.objects.order_by('-pub_date')
context['avaibilitys'] = avaibilitys
return context
models.py code
class Avaibility(models.Model):
name_text = models.CharField(max_length=200)
apply_text = models.CharField(max_length=200)
presence_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published', null=True)
def __str__(self):
return self.name_text
def __str__(self):
return self.apply_text
def __str__(self):
return self.presence_text
this is what displays
You are just sorting the data using order_by and assign the sorted data to a variable:
avaibilitys = Avaibility.objects.order_by('-pub_date')
If you want to get only one of them, you can do this:
avaibilitys = Avaibility.objects.order_by('-pub_date').first()
EDIT
If you want the last one, do this:
avaibilitys = Avaibility.objects.order_by('-pub_date').last()
I understand this particular error message
'QuerySet' object has no attribute '_meta'
has been discussed a lot on StackOverflow and I have gone through lots of the answers provided but each is unique and didn't solve my problem.
So, I have a list of filtered model objects I'm getting from a database:
questions_by_category = Question.objects.filter(category=category_id)
I want to save this list in the session like this:
request.session["questions"] = json.dumps(model_to_dict(questions_by_category))
but I'm getting the error message specifically from this line:
model_to_dict(questions_by_category)
This is the model class:
class Question(models.Model):
question_text = models.CharField(max_length=200)
correct_answer = models.CharField(max_length=20)
publication_date = models.DateTimeField('date_published', default=django
.utils.timezone.now)
question_hint = models.CharField(max_length=200, default='hint')
question_thumbnail = models.ImageField(upload_to='gallery', height_field=None, width_field=None,
max_length=100,
default='images/pyramid.jpg')
category = models.ForeignKey(QuestionCategory, on_delete=models.SET_NULL, null=True)
difficulty_level = models.IntegerField(default=10)
def was_published_recently(self):
return self.publication_date >= timezone.now() - datetime.timedelta(days=1)
class Meta:
db_table = 'question'
def __str__(self):
return self.question_text
def serialize(self):
return self.__dict__
And the view:
def question(request, category_name, category_id):
questions_by_category = Question.objects.filter(category=category_id)
current_question = questions_by_category.iterator().__next__()
choice = current_question.choice_set.get()
form = ChoiceForm(request.POST)
request.session["questions"] = json.dumps(model_to_dict(questions_by_category))
context = {
'question': current_question, 'choice': choice, 'form': form
}
return render(request, 'quiz/question.html', context)
EDIT
This the other view where I intend to modify the list:
def accept_choice(request):
global data
if request.method == 'POST':
data = request.POST.get('choiceRadioGroup')
print('Selected data: ' + str(data))
return render(request, 'quiz/question.html', {'data': 'data'}
The goal here (which is starting to appear messy) is to select accept a choice from the question view, on next button click, accept_choice is called, and the next question id displayed. My intention is to keep track of the current question by maintaining the list of questions in a session.
I'd really appreciate an explanation on what I'm doing wrong and the right way to go about this.
I am just starting to learn about Django and I am running into an issue. I have a modelFormSet that is meant to add 3 choices to a question you create. I have no issue when I am not using the modelFormset and only adding one question but when I try to iterate through a modelsFormset and assign each choice to the question that was just created I get the following error:
NOT NULL constraint failed: polls_choice.question_id
I think it has something to do with the question_id not being passed to the choice model but I am not sure how to fix it. I have run fresh migrations and I don't think I can set blank or null to True since I need the choice and question to be related. Thank you in advance for your help!
Models
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
assigned_to = models.ManyToManyField(User)
def __str__(self):
return self.question_text
def was_published_recently(self):
now = timezone.now()
return now - datetime.timedelta(days=1) <= self.pub_date <= now
was_published_recently.admin_order_field = 'pub_date'
was_published_recently.boolean = True
was_published_recently.short_description = 'Published recently?'
class Choice(models.Model):
question = models.ForeignKey(Question)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __str__(self):
return self.choice_text
Forms
class CreateQuestion(forms.ModelForm):
class Meta:
model = Question
fields = ('question_text', 'assigned_to', 'pub_date',)
class AddChoices(forms.ModelForm):
class Meta:
model = Choice
fields = ('choice_text',)
View
def create_question(request):
choices_formset = modelformset_factory(Choice, form=AddChoices, fields=('choice_text',), extra=3)
if request.method == 'POST':
question_form = CreateQuestion(data=request.POST)
choice_form = choices_formset(request.POST, request.FILES)
if question_form.is_valid and choice_form.is_valid:
question = question_form.save()
for choice in choice_form:
choice.question = question
choice.save()
return HttpResponseRedirect(reverse('polls:index'))
else:
return render(request, 'polls/createquestion.html', {'question_form': question_form,
'choice_form': choice_form, })
else:
question_form = CreateQuestion()
choice_form = choices_formset(queryset=Choice.objects.none(),)
return render(request, 'polls/createquestion.html', {'question_form': question_form,
'choice_form': choice_form, })
When you loop through for choice in choice_form, each item is a form, so setting the question attribute doesn't work.
Instead, you should save with commit=False, set the question, then save the object to the db.
for form in choice_form:
choice = form.save(commit=False)
choice.question = question
choice.save()
Problem is here:
for choice in choice_form:
choice.question = question
choice.save()
You're iterating here over AddChoices forms, not over Choice objects. That mean, you're saving question as an attribute of form, not as attribute of model instance and that won't propagate into model instance.
To fix it you can try:
for form in choice_form:
choice = form.save(commit=False)
choice.question = question
choice.save()
I'm making a forum kind of site where a patient can ask a question to doctors. And I'm confused about some idea. Here is my Question model:
class Question(models.Model):
user = models.ForeignKey(User)
title = models.CharField(max_length=120)
description = models.TextField()
category = models.ForeignKey('Category')
answers = models.ManyToManyField('Answer',related_name='answer_name', blank=True)
post_date = models.DateTimeField(auto_now=True)
published = models.BooleanField(default=False)
vote = models.IntegerField() # Field that denotes the no of vote for particular question
def __unicode__(self):
return self.title
I have created a form for asking question and that works well. What I want is that in the detail view of the question there would be the answer.
Here is my model and view for answer:
class Answer(models.Model):
user = models.ForeignKey(User)
question = models.ForeignKey(Question)
ans_body = models.TextField()
comments = models.ManyToManyField('Comment',related_name='answer_name', blank=True)
count = models.IntegerField()
post_date = models.DateTimeField(auto_now=True)
def __unicode__(self):
return self.ans_body
And view is:
class QuestionDetailView(DetailView):
context = {}
model = Question
form_class = AnswerCreationForm
template_name = "question-detail.html"
def get(self, request, pk, **kwargs):
self.pk = pk
print self.pk
return super(QuestionDetailView, self).get(request, pk, **kwargs)
def get_context_data(self, **kwargs):
context = super(QuestionDetailView,self).get_context_data(**kwargs)
context['question'] = Question.objects.get(pk=self.pk)
context['form'] = AnswerCreationForm
return context
Here I have passed context as form. I have created the form too and the fields are user, question and ans_body.
Now I want to save the ans_body, set user to the current user and question to the same question in detail view.
I'm stucked. I don't know what I'm doing is right. Is there any better solution?? Thanks in advance
You can override the save method of form, it similar to the models save method eg.
def save(self, commit=True, **kwargs):
user = kwargs.pop('user')
self.user = user
super(Classname, self).save(commit=True)
I have a question model and a choices model. A choice can be correct or not.
class Choice(models.Model):
question = models.ForeignKey('Question', related_name='choices')
choice = models.CharField(max_length=255)
is_correct = models.BooleanField(default=False)
times_chosen = models.IntegerField(editable=False, default=0)
def __unicode__(self):
return self.choice + ' / ' + str(self.times_chosen)
#multiple choice question
class Question(models.Model):
def _get_average(self):
"Returns the average in percent"
if self.times_total == 0:
return 0.0
return (self.times_correct / float(self.times_total)) * 100
def _get_answer(self):
"Returns the answer"
for choice in self.choices.all():
if choice.question == self and choice.is_correct:
return choice.choice
return None
def __unicode__(self):
return self.question
question = models.CharField(max_length=255)
modules = models.ManyToManyField(Module, related_name='questions')
creator = models.CharField(max_length=255)
last_updated = models.DateTimeField(auto_now=True)
#used for global statistics per question
times_correct = models.IntegerField(editable=False, default=0)
times_total = models.IntegerField(editable=False, default=0)
#Derived values
average = property(_get_average)
answer = property(_get_answer)
First I tried only saving when there was an answer.
def save(self):
" Make sure that a question has at least one answer "
if self._get_answer():
super(Question, self).save()
But Question can't save because it has no answer set, and it can't have an answer set until its saved.
So I guess whenever I have a Question form I need to check if it has an answer before it is valid.
The form is in the admin and it uses inlines. So I created a new form class and would like to use it in the admin instead.
class ChoiceInline(admin.TabularInline):
model = Choice
extra = 4
#TODO: move?
class QuestionAdminForm(forms.ModelForm):
class Meta:
model = Question
def clean(self):
data = self.cleaned_data
logger.info(data)
data = self.cleaned_data['choices']
logger.info(data)
#if "fred#example.com" not in data:
# raise forms.ValidationError("You have forgotten about Fred!")
# Always return the cleaned data, whether you have changed it or
# not.
return data
class QuestionAdmin(admin.ModelAdmin):
readonly_fields = ('average', 'last_updated')
#list_display = ["question", "module", "average", "quiz"]
#can't have below because M2M question-> module
#list_display = ["question", "module", "average"]
list_display = ["question", "average"]
list_display_links = ["question"]
list_filter = ['modules__name']
search_fields = ["question", "modules__name", "quiz__name"]
inlines = [ChoiceInline]
actions = [duplicate_questions]
form = QuestionAdminForm
However self.cleaned_data doesn't contain choices. So I can't use that to validate if one of them is the answer.
EDIT
Here is the POST data
creator
u'Siecje'
choices-0-is_correct
u'on'
choices-1-choice
u'No'
choices-0-id
u''
choices-__prefix__-question
u''
choices-1-id
u''
question
u'Question Four?'
choices-0-question
u''
csrfmiddlewaretoken
u'hfRAW8B03as6XN5GpIygJ642VKMN2TPa'
choices-__prefix__-id
u''
choices-3-id
u''
_save
u'Save'
choices-2-question
u''
choices-2-id
u''
choices-MAX_NUM_FORMS
u'1000'
choices-INITIAL_FORMS
u'0'
choices-3-question
u''
choices-3-choice
u'So'
choices-0-choice
u'Yes'
choices-__prefix__-choice
u''
choices-1-question
u''
modules
u'24'
choices-2-choice
u'Maybe'
choices-TOTAL_FORMS
u'4'
This is what I ended up doing based on Django admin validation for inline form which rely on the total of a field between all forms
class CombinedFormSet(BaseInlineFormSet):
# Validate formset data here
def clean(self):
super(CombinedFormSet, self).clean()
for form in self.forms:
if not hasattr(form, 'cleaned_data'):
continue
data = self.cleaned_data
valid = False
for i in data:
if i != {}:
if i['is_correct']:
valid = True
if not valid:
#TODO: translate admin?
raise forms.ValidationError("A Question must have an answer.")
# Always return the cleaned data, whether you have changed it or
# not.
return data
class ChoiceInline(admin.TabularInline):
model = Choice
extra = 4
formset = CombinedFormSet