django complex formset issue - python

I have 3 primary models. Questionnaire model or question set contains set of questions. All user response are stored in answer.
Now I have to generate a formset which will store answer of all questions in a questionnaire set. How can I do it in django. So far I have manged to do it by displaying single question at once from a given questionnaire and store the response. My problem is that based on questiontype use two different modelform (MultipleChoiceAnswerForm,DescriptiveChoiceAnswerForm) and validate them based on the formtype. How can I use it in formset.
I am beginner in django and any help is appreciated.
My Code:
#Models.py
class Question(models.Model):
statement = models.CharField(max_length=255)
question_type = models.CharField(max_length=20, choices=get_qtypes())
remarks = models.CharField(max_length=200, null=True, blank=True)
def __unicode__(self):
return '%s'%(self.statement)
class Questionnaire(models.Model):
title = models.CharField(max_length=255)
questionaire_type = models.CharField(max_length=20,choices=QUESTIONNAIRETYPE)
context = models.ForeignKey(QuestionContext)
questions = models.ManyToManyField(Question)
timestamp = models.DateTimeField(auto_now=True)
tathya_user = models.ForeignKey(User)
def __unicode__(self):
return '%s'%(self.title)
class Answer(models.Model):
question = models.ForeignKey(Question)
person = models.ForeignKey(Person)
course = models.ForeignKey(Course)
teacher=models.ForeignKey(Person, null=True, blank=True, default = None)
questionaire = models.ForeignKey(Questionnaire)
statement = models.CharField(max_length=255)
def get_label(self):
return '%s'%(self.question.statement)
def get_choices(self):
return get_questionchoices(self.question.question_type)
class DescriptiveAnswerForm(ModelForm):
def __init__(self, *args, **kwargs):
super(DescriptiveAnswerForm, self).__init__(*args, **kwargs)
if kwargs.has_key('instance'):
self.fields['statement'].label = kwargs['instance'].get_label()
statement = forms.CharField(widget=forms.Textarea())
class Meta:
model = Answer
exclude=('question','person','course','teacher','questionaire')
class MultipleChoiceAnswerForm(ModelForm):
statement = forms.ChoiceField(widget=forms.RadioSelect(choices=EMPTY,attrs={'class': 'allradio',}))
def __init__(self, *args, **kwargs):
super(MultipleChoiceAnswerForm, self).__init__(*args, **kwargs)
if kwargs.has_key('instance'):
self.fields['statement'].label = kwargs['instance'].get_label()
self.fields['statement'].choices = kwargs['instance'].get_choices()
class Meta:
model = Answer
exclude=('question','person','course','teacher','questionaire')
###################################################################
#view.py
#login_required
def content_feedback_view_old(request,course_code):
#do validation and other jobs
questionnaire = get_questionnaire(some_params_like_coursecode)
if request.method == 'POST':
r_answer = Answer()
r_answer.question = Question.objects.get(id=request.session['question'])
r_answer.person = student
r_answer.course = course
r_answer.questionaire = questionnaire
r_answer.tathya_user = User.objects.get(id=request.user.pk)
rformtype = request.POST['formtype']
if rformtype == 'MCQ':
rform = MultipleChoiceAnswerForm(request.POST, instance=r_answer)
else:
rform = DescriptiveAnswerForm(request.POST, instance=r_answer)
if rform.is_valid():
rform.save()
else:
#return HttpResponse(printerror("Some problem occurred!"))
errortext = "You need to provide an input!"
questions = questionnaire.questions.all()
allquestions = questions.count()
tot_q = 0
formtype = ""
answered = 0
for question in questions:
try:
answer=Answer.objects.get(question=question,person=student,course=course,questionaire=questionnaire)
answered += 1
except:
answer = Answer()
answer.question = question
answer.person = student
answer.course = course
answer.questionaire = questionnaire
answer.tathya_user = User.objects.get(id=request.user.pk)
request.session['question']=question.id
tot_q = tot_q + 1;
if get_questiontype(question.question_type)=='MCQ':
formtype="MCQ"
form=MultipleChoiceAnswerForm(instance=answer)
else:
formtype="DESC"
form=DescriptiveAnswerForm(instance=answer)
break
if tot_q>0:
data_dict['FeedbackFormType']=formtype
data_dict['FeedbackForm']=form
data_dict['pagetitle']=context.description
data_dict['coursecode']=course.course_code
data_dict['feedbacktitle']="Content Feedback for "+course.fullname
data_dict['Completeness'] = (answered/allquestions)*100
data_dict['error']=errortext
else:
return HttpResponse(printerror("Thanks! You've answered all the questions!<br>Continue with the teaching feedback."))
req_context = RequestContext(request)
return render_to_response('view.html', data_dict, req_context)

Simple answer: only use on single AnswerForm and let it manage which kind of field and widget it should use, ie:
class AnswerForm(ModelForm):
def __init__(self, *args, **kwargs):
super(AnswerForm, self).__init__(*args, **kwargs)
instance = self.instance
if instance.question.question_type == 'MCQ':
self.fields["statement"] = forms.ChoiceField(
choices=instance.get_choices(),
widget=forms.RadioSelect(attrs={'class': 'allradio',})
)
else:
self.fields["statement"] = forms.CharField(
widget=forms.Textarea()
)
self.fields['statement'].label = instance.get_label()
class Meta:
model = Answer
exclude=('question','person','course','teacher','questionaire')
As a side note, you can pass model's attributes values to the model's constructor:
answer = Answer(
question=Question.objects.get(id=request.session['question']),
person=student,
course=course,
questionnaire=questionnaire,
# User.objects.get(id=request.user.pk) will return request.user
# so it's just useless - just use request.user
tathya_user=request.user
)

Related

Updating a field within another Model's save method with F()

I'm attempting to create a voting system where the score of a given post is separate from the type of votes placed by users. In the event that a user deletes a profile, a given score should not increment/decrement due to their vote being deleted. Therefore scores are only updated by using an F('score') + 1 or F('score') - 1 expression.
Within Vote.save(), I'm trying to implement this, yet the Question.score field doesn't update when the Vote is created. How can I get the test to pass where the score in the question goes from 0 to 1? django.db.models.F is in fact being imported into the module but it's not displayed here.
class TestQuestionScoreUpVote(TestCase):
'''Verify that a Question's score increments by one point
when the Vote is an "up" vote.'''
#classmethod
def setUpTestData(cls):
tag1 = Tag.objects.create(name="Tag1")
user = get_user_model().objects.create_user("TestUser")
profile = Profile.objects.create(user=user)
cls.question = Question.objects.create(
title="Question__001",
body="Content of Question 001",
profile=profile
)
cls.question.tags.add(tag1)
user_vote = Vote.objects.create(
profile=profile, type="upvote", content_object=cls.question
)
def test_new_user_vote_on_question(self):
self.assertEqual(self.question.score, 1)
class Post(Model):
body = TextField()
date = DateField(default=date.today)
comment = ForeignKey('Comment', on_delete=CASCADE, null=True)
profile = ForeignKey(
'authors.Profile', on_delete=SET_NULL, null=True,
related_name='%(class)ss',
related_query_name="%(class)s"
)
vote = GenericRelation(
'Vote', related_query_name="%(class)s"
)
score = IntegerField(default=0)
class Meta:
abstract = True
class Question(Post):
title = CharField(max_length=75)
tags = ManyToManyField(
'Tag', related_name="questions", related_query_name="question"
)
views = IntegerField(default=0)
objects = Manager()
postings = QuestionSearchManager()
class Meta:
db_table = "question"
ordering = ["-score" , "-date"]
def __repr__(self):
return f"{self.__class__.__name__}(title={self.title})"
class Vote(Model):
profile = ForeignKey(
'authors.Profile', on_delete=SET_NULL, null=True,
related_name="votes"
)
type = CharField(max_length=7)
content_type = ForeignKey(ContentType, on_delete=CASCADE)
object_id = PositiveIntegerField()
content_object = GenericForeignKey()
def save(self, *args, **kwargs):
post = ContentType.objects.get_for_id(
self.content_type_id
).get_object_for_this_type(id=self.object_id)
if self.type == "upvote":
post.score = F("score") + 1
else:
post.score = F("score") - 1
post.refresh_from_db()
super().save(*args, **kwargs)
You are forgetting to call post.save() after the object change:
def save(self, *args, **kwargs):
post = ContentType.objects.get_for_id(
self.content_type_id
).get_object_for_this_type(id=self.object_id)
if self.type == "upvote":
post.score = F("score") + 1
else:
post.score = F("score") - 1
post.save() # <- HERE
post.refresh_from_db()
super().save(*args, **kwargs)

Django order_by randomizes queryset twice?

I am trying to randomize a query set for a program i'm working on.
This is what's is in my views.py
full_quiz = Quiz.objects.get(name=quiz_name).questions.all()
form = TheForm(full_quiz)
if request.method == "POST":
form = QuestionForm(request.GET, full_quiz)
This is what is in my forms.py
class QuestionForm(forms.Form):
def __init__(self, questions, *args, **kwargs):
super(QuestionForm, self).__init__(*args, **kwargs)
for i in range(len(questions)):
if questions[i].answer.count() < 2:
self.fields["field_name %d" % i] = forms.CharField(label=questions[i], required=False)
From my testing, it appears like the query set is getting randomized with the second form variable. Any fix for this?
Thanks
You can get a random order queryset as described here:
full_quiz = Quiz.objects.get(name=quiz_name).questions.order_by('?')
You have not posted the code of your forms but I expect QuestionForm is a ModelFormSet. You can change the queryset of a ModelFormSet by setting the queryset arg (see docs):
form = QuestionForm(queryset=fullquiz)
Suppose you have a model structure like this
class Quiz(models.Model):
name = models.CharField('Quizname', max_length=200)
questions = models.ManyToManyField('Question')
class Answer(models.Model):
reply = models.CharField('Answer', max_length=100)
class Question(models.Model):
the_question = models.CharField('Question', max_length=100)
answers = models.ManyToManyField('Answer', related_name='answers')
correct_answer = models.ForeignKey('Answer', on_delete=models.CASCADE)
def is_correct(self, given_answer):
return given_answer == self.correct_answer
you could create a form structure with a ModelForm and a ModelFormSet:
class QuestionForm(forms.ModelForm):
myanswers = forms.ModelChoiceField(queryset=Answer.objects.all(), widget=forms.RadioSelect, required=False)
class Meta:
model = Question
fields = ('the_question', 'myanswers')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['myanswers'].queryset = self.instance.answers.all()
QuestionFormSet = modelformset_factory(Question, form=QuestionForm, extra=0)
And use it in a view:
def myview(request, quiz_name):
if request.method == 'POST':
formset = QuestionForm(request.POST)
if form.is_valid():
allanswers = [f['id'].is_correct(f['myanswers']) for f in formset.cleaned_data]
print('Number of correct answers:', sum(allanswers))
fullquiz = Quiz.objects.get(name=quiz_name).questions.order_by('?')
formset = QuestionForm(queryset=fullquiz)
return render(request, 'yourtemplate.html', {'formset': formset })

Using Dynamic Variable to populate Form label/verbose name

I am new to Django. I want to populate a form for model Score with dynamic labels/verbose names from another model : Question.
Essentially, each user has 5 current questions which they are going to score in a form as either "yes" or "no"
I have the data for a User's current five questions saved in a dictionary and can pass this dictionary into the view or template, but do not know how to use a dictionary to population the labels/ verbose names for the form.
#Model
class Score(models.Model):
yesno = ((0,"No"),(1,"Yes"),)
oneScore =models.IntegerField(choices=yesno,default='none')
twoScore =models.IntegerField(choices=yesno,default='none')
threeScore =models.IntegerField(choices=yesno,default='none')
fourScore =models.IntegerField(choices=yesno,default='none')
fiveScore =models.IntegerField(choices=yesno,default='none')
author = models.ForeignKey('auth.User')
date_created = models.DateTimeField(blank=False, null=False)
#Model
class QuestionManager(models.Manager):
def current_for_user(self,user):
question1 = Question.objects.filter(author=user,statementNumber=1).order_by('-id').first()
question2 = Question.objects.filter(author=user,statementNumber=2).order_by('-id').first()
question3 = Question.objects.filter(author=user,statementNumber=3).order_by('-id').first()
question4 = Question.objects.filter(author=user,statementNumber=4).order_by('-id').first()
question5 = Question.objects.filter(author=user,statementNumber=5).order_by('-id').first()
question_list = {"1":question1,
"2":question2,
"3":question3,
"4":question4,
"5":question5}
return question_list
class Question(models.Model):
statementNumber=models.IntegerField(choices=NUMBER, default='1',verbose_name="The number of the statement")
text = models.CharField(max_length=500,help_text="Enter your text", verbose_name="New Statement")
author = models.ForeignKey('auth.User')
date_created = models.DateTimeField(blank=False, null=False)
objects=QuestionManager()
#Form
class ScoreForm(forms.ModelForm):
class Meta:
model = Score
fields = ('oneScore','twoScore','threeScore','fourScore','fiveScore','bigScore')
#View
def score(request):
user = request.user
questions = Question.objects.current_for_user(user)
if request.method == "POST":
questions = Question.objects.current_for_user(user)
form = ScoreForm(request.POST)
if form.is_valid():
score = form.save(commit=False)
score.author = request.user
score.save()
return redirect('scoresheet',pk=score.pk)
else:
form = ScoreForm()
return render(request,'MC/score.html',{'form': form,'questions':sorted(questions.items())})
I'm not really sure what you're after, but you can pass anything you like to the form:
class ScoreForm(forms.ModelForm):
def __init__(self, questions, *args, **kwargs):
super().__init__(*args, **kwargs)
for num, question in questions.items():
self.fields.get('some_field').label = question
#View
def score(request):
user = request.user
questions = Question.objects.current_for_user(user)
if:
# ...
else:
form = ScoreForm(questions)
return render(request,'MC/score.html',{'form': form})

submit form as superuser vs regular user

In my django app I have a Myuser(User) class. It inherits the User class.
When a new user is created the Myuser table is poplulated.
myusers.py
class Myuser(User):
address = models.CharField(max_length=40)
pobox = models.CharField(max_length=40)
models.py
class Someclass(models.Model):
objectid = models.IntegerField()
objecttype = models.CharField(max_length=200)
created = models.DateTimeField(default=timezone.now)
modified = models.DateTimeField(auto_now=True)
class Someotherclass(Someclass):
status = models.IntegerField(default=0,)
name = models.CharField(max_length=200)
created = models.DateTimeField(default=timezone.now)
modified = models.DateTimeField(auto_now=True)
user = models.ForeignKey(User)
forms.py
class SomeotherclassForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop("request")
self.user = kwargs.pop('user')
self.app = kwargs.pop('app')
self.table = kwargs.pop('table')
self.mytype = kwargs.pop('mytype')
initial = kwargs.get('initial', {})
super(SomeotherclassForm, self).__init__(*args, **kwargs)
create.py
class DataCreate(CreateView):
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
#some code here not relevant at all
def get_form_kwargs(self):
kwargs = super(DataCreate, self).get_form_kwargs()
objectid = self.request.GET.get('objectid',None)
objecttype = self.request.GET.get('objecttype',None)
kwargs.update({'mytype': objecttype})
kwargs.update({'request': self.request})
kwargs.update({'user': self.request.user})
kwargs.update({'app': self.app})
kwargs.update({'table': self.kwargs['table'].lower()})
return kwargs
def form_valid(self, form):
obj = form.save(commit=False)
group = ''
if not self.request.user.is_superuser:
group = MyUser.objects.get(user_ptr_id=self.request.user.pk)
else:
groups = self.request.user.groups.all()
if self.kwargs['table'] == 'Myprotocol':
obj = form.save(commit=False)
table = eval(self.request.GET.get('objecttype',None).title()).objects.get(pk=int(self.request.GET.get('objectid',None)))
obj.objectid = table.pk
obj.objecttype = table.__class__.__name__.lower()
obj.user_id = self.request.user.pk
obj.save()
else:
obj = form.save()
if self.request.POST.get('is_popup'):
check = int(self.kwargs['is_list'])
if self.kwargs['table'] == 'Someclass':
popup = 1
a = checkPopup2(obj,check,popup,obj.pk)
else:
a = checkPopup(obj,check)
return a
else:
return super(DataCreate, self).form_valid(form)
When I have logged in as a regular user ,everything works fine.
When I log in as a superuser, I get form error that objecttype,objectid and user are not filled.
In my attempts to troubleshoot it , I realized that when I am logged in as a superuser ,it dowsn't reach the form_valid() function.
I can't figure out why that is happening.
Any suggestions or advice on how to troubleshoot it?

django using form as a context and saving it with current user

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)

Categories

Resources