I want to write a website that gives elementary math tasks to its visitors, quite similar to this: http://jsfiddle.net/r4QTQ/
In contrast to the JS example my idea is for the visitor to finish all tasks first, then press a button "check answers" and have all answers marked with a smiley if answer is right or the correct result if visitor's answer is wrong. To this - if I'm right - it's necessary to redirect the visitor's answers and the task-list to a different view. Also I want to use Django templates and CBviews.
I have this view for setting up the math tasks - view.py:
class Plus_im_10erPageView(TemplateView):
form_class = AnswerForm
template_name = 'plus_im_10er.html'
success_html = 'plus_im_10er_check.html'
def tasks(self):
# make a list of math tasks here
return task_list
def get_context_data(self, **kwargs):
context = super(Plus_im_10erPageView, self).get_context_data(**kwargs)
task_list = self.tasks()
context['tasks'] = task_list
self.context = context
return context
def post(self, request):
form = AnswerForm(request.POST)
if form.is_valid():
answers = form.cleaned_data
print(answers)
return render(request, self.success_html, {'answers': answers}
The AnswerForm is a collection of 10 CharFields like this - forms.py:
class AnswerForm(forms.Form):
answer_1 = forms.CharField(label='answer', required=False)
answer_2 = forms.CharField(label='answer', ...
...
Next is the template - plus_im_10er.html:
{% extends 'base.html' %}
{% block content %}
<ul>
<form method="POST">{% csrf_token %}
{% for task in tasks %}
<li>{{ task.0 }} + {{ task.1 }} =
<input type="text" size=2 name="answer_list"
onkeypress="return event.charCode >= 48 && event.charCode <=57">{{ answer }}
</input>
</li>
{% endfor %}
</br>
</br>
<input type="button" onclick="location.href='/plus_im_10er_check/';"
value="Check answers">
</input>
</ul>
{% endblock content %}
This works so far.
What I cannot figure out is how to continue. My idea is to write a second view called Plus_im_10er_checkPageView and a second template called plus_im_10er_check.html but whatever I tried didn't get me anywhere. I spare you my attempts. How would you tackle this?
This is a working solution. It's not pretty, so I would appreciate ideas to refacture it.
Starting with view.py:
class Plus_im_10erPageView(TemplateView):
template_name = 'plus_im_10er.html'
success_html = 'plus_im_10er_check.html'
no_of_tasks = 3
#classmethod
def generate_plus10_tasks(self):
"""
Generate tasks containing two addends with a sum less or equal 10.
"""
task_list = []
for _ in range(self.no_of_tasks):
while True:
x = random.randint(0, 9)
y = random.randint(0, 9)
result = x + y
if result <= 10:
task_list.append((x, y, result))
break
self.task_list = task_list
return task_list
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) # noqa
context['task_list'] = self.generate_plus10_tasks()
self.context = context
return context
def post(self, request):
form = AnswerForm(request.POST)
answered_tasks = []
if form.is_valid():
answers = form.cleaned_data
total_correct_answers = 0
# helper to get the form_field name
index = 1
for task in self.task_list:
if index > self.no_of_tasks:
break
answer = int(answers[f'answer_{index}'])
x, y, correct_result = task
if answer == correct_result:
total_correct_answers += 1
answered_tasks.append((x, y, correct_result, answer,
total_correct_answers)) # noqa
index += 1
return render(request,
self.success_html,
{'answered_tasks': answered_tasks}
)
The forms:
from django import forms
class AnswerForm(forms.Form):
answer_1 = forms.CharField(label='answer', required=False)
answer_2 = forms.CharField(label='answer', required=False)
answer_3 = forms.CharField(label='answer', required=False)
answer_4 = forms.CharField(label='answer', required=False)
answer_5 = forms.CharField(label='answer', required=False)
answer_6 = forms.CharField(label='answer', required=False)
answer_7 = forms.CharField(label='answer', required=False)
answer_8 = forms.CharField(label='answer', required=False)
answer_9 = forms.CharField(label='answer', required=False)
answer_10 = forms.CharField(label='answer', required=False)
plus_im_10er.html
<!-- templates/plus_im_10er.html -->
{% extends 'base.html' %}
{% block content %}
<h2>Plus rechnen im 10er-Raum</h2>
<ul>
<form method="POST">{% csrf_token %}
{% for task in task_list %}
<li><label for="id_answer">{{ task.0 }} + {{ task.1 }} = </label>
<input id="id_answer" type="text" size=2 name="answer_{{ forloop.counter }}"
onkeypress="return event.charCode >= 48 && event.charCode <=57">{{ form }}
</input>
</li>
{% endfor %}
</ul>
</br></br>
<form action="/plus_im_10er_check/" method="GET">
<input type="submit" value="Antworten prüfen">
</form>
{% endblock content %}
plus_im_10er_check.html
<!-- templates/plus_im_10er_check.html -->
{% extends 'base.html' %}
{% block content %}
<h2>Plus rechnen im 10er-Raum</h2>
<h3>Antworten prüfen</h3>
<ul>
<div>
{% for task in answered_tasks %}
{% if task.2 == task.3 %}
<li>{{ task.0 }} + {{ task.1 }} = {{ task.3 }} 😃</li>
{% else %}
<li>{{ task.0 }} + {{ task.1 }} ≠ {{ task.3 }}</li>
{% endif %}
{% endfor %}
</br>
Du hast {{ answered_tasks.2.4 }} 😃 von 3.
</div>
</br></br>
<button type="button">Neue Aufgaben</button>
</ul>
{% endblock content %}
Ande the result looks like this:
Related
My application has two models, Recipe and RecipeIngredient, related by foreign key. Each Recipe may have many different RecipeIngredients (and different number of associated ingredients for each recipe). So using a dynamic formset to assign ingredients to a recipe seems to be the best approach.
I have a simple form that has a user enter a quantity and select a unit and ingredient from a dropdown. As a standalone form it works fine.
Unfortunately, I cannot get any formset incorporating it to submit.
I have searched many of the different formset Q&As here on StackOverflow but cannot see where the problem is. When I click 'submit', the form blinks and refreshes in place, with the entered values still in the form. No objects are created. No errors report to the console nor to the browser. The related code:
The models:
class RecipeBase(models.Model):
id = models.UUIDField(primary_key=True,default=uuid.uuid4,null=False)
name = models.CharField(max_length=128,null=False)
creation_date = models.DateField("Record creation date",auto_now_add=True)
creation_user = models.CharField("Record creation user",max_length=150)
lastupdated_date = models.DateField(auto_now=True)
lastupdated_user = models.CharField(max_length=150)
category = models.ForeignKey(RecipeCategory,on_delete=models.CASCADE,related_name='recipes')
subcategory = models.ForeignKey(RecipeSubcategory,on_delete=models.CASCADE,related_name='recipes')
def __str__(self):
return str(self.name)
class RecipeIngredient(models.Model):
id = models.UUIDField(primary_key=True,default=uuid.uuid4,null=False)
quantity = models.DecimalField(max_digits=8,decimal_places=3,null=False)
referenced_unit = models.ForeignKey(UnitDetail,on_delete=models.CASCADE)
referenced_ingredient = models.ForeignKey(Ingredient,on_delete=models.CASCADE)
parent_recipe = models.ForeignKey(RecipeBase,on_delete=models.CASCADE,related_name='ingredients')
def __str__(self):
return str(self.id)[:8]
The call to enter ingredients:
<a class='btn btn-success' href="{% url 'recipes:addingredient' pk=recipe_details.pk %}">Add Ingredients</a>
Calls the view:
class AddIngredientView(CreateView):
form_class = AddIngredientForm
model = RecipeIngredient
template_name = 'addingredientmultiple2.html'
success_url = "recipes:listrecipes"
def get_context_data(self, **kwargs):
parent_recipe_id = RecipeBase.objects.get(id=self.kwargs['pk'])
data = super(AddIngredientView, self).get_context_data(**kwargs)
if self.request.POST:
data['ingredients'] = AddIngredientFormset(self.request.POST)
data['parent_recipe_id'] = parent_recipe_id
else:
data['ingredients'] = AddIngredientFormset()
data['fixture'] = parent_recipe_id
return data
def form_valid(self, form, **kwargs):
user = self.request.user
fixture = RecipeBase.objects.get(id=self.kwargs['pk'])
context = self.get_context_data()
formset = AddIngredientFormset(self.request.POST)
if formset.is_valid():
ingredients = formset.save()
for recipeingredient in ingredients:
#recipeingredient.creation_user = str(request.user)
#recipeingredient.lastupdated_user = str(request.user)
recipeingredient.parent_recipe_id = parent_recipe_id
#recipeingredient.user = user
recipeingredient.save()
return super(AddIngredientView, self).form_valid(form)
Using form and formset:
class AddIngredientForm(forms.ModelForm):
quantity = forms.DecimalField(widget=forms.NumberInput(attrs={'data-placeholder': 0.00,'size': '8','label_tag': ''}))
referenced_unit = forms.ModelChoiceField(queryset=UnitDetail.objects.all())
referenced_ingredient = forms.ModelChoiceField(queryset=Ingredient.objects.all())
class Meta():
model = RecipeIngredient
fields = ('quantity','referenced_unit','referenced_ingredient',)
AddIngredientFormset = inlineformset_factory(RecipeBase,RecipeIngredient,form=AddIngredientForm,extra=1,can_delete=True)
And template:
{{ ingredients.media.css }}
<div class="container">
<h2>Add ingredients to {{ referring_recipe_name }}</h2>
<h4>{{ referring_recipe_id }}</h4>
</div>
<div class="col-md-4">
<form class="form-horizontal" enctype="multipart/form-data" method="post">{% csrf_token %}
<table class="table">
{{ ingredients.management_form }}
{% for form in ingredients.forms %}
{% if forloop.first %}
<thead>
<tr>
{% for field in form.visible_fields %}
<th>{{ field.label|capfirst }}</th>
{% endfor %}
</tr>
</thead>
{% endif %}
<tr class="formset_row">
{% for field in form.visible_fields %}
<td>
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<input class='btn btn-success' type="submit" value="Save"/> back to the list
</form>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="{% static 'formset/jquery.formset.js' %}"></script>
<script type="text/javascript">
$('.formset_row').formset({
addText: 'ADD INGREDIENT',
deleteText: 'REMOVE',
prefix: 'ingredients'
});
</script>
{{ ingredients.media.js }}
Update:
The issue seemed to be in the coding for Django-formset. I was processing it as an inline formset and not a model formset. The answer below was also correct. Thanks!
I am working with a model formset for an intermediate model. I am using django-formset js to add additional formset fields on the template. Most everything works OK except that when I go to save the formset only the first entry is being saved to the DB. The first entry is saved and assigned correctly but any after than just disappear. It is not throwing any errors so I am not sure what is going wrong. Thanks!
The Model
class StaffAssignment(models.Model):
study = models.ForeignKey(Study, related_name='study_set', null=True, on_delete=models.CASCADE)
staff = models.ForeignKey('account.UserProfile', related_name='assigned_to_set', null=True, on_delete=models.CASCADE)
role = models.CharField(max_length=100, null=True)
assigned_on = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('-role',)
def __str__(self):
return '{} is assigned to {}'.format(self.staff, self.study)
The Form:
class AddStaff(forms.ModelForm):
model = StaffAssignment
fields = ('staff',)
def __init__(self, *args, **kwargs):
super(AddStaff, self).__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].widget.attrs.update({'class': 'form-control'})
The View:
def add_staff(request, study_slug):
study = get_object_or_404(Study, slug=study_slug)
staff_formset = modelformset_factory(StaffAssignment, form=AddStaff, fields=('staff',), can_delete=True)
if request.method == 'POST':
staffList = staff_formset(request.POST, request.FILES)
if staffList.is_valid():
for assignment in staffList:
assigned = assignment.save(commit=False)
assigned.study = study
assigned.role = assigned.staff.job_title
assigned.save()
return HttpResponseRedirect(reverse('studies:studydashboard'))
else:
HttpResponse('Something is messed up')
else:
staffList = staff_formset(queryset=StaffAssignment.objects.none())
return render(request, 'studies/addstaff.html', {'staffList': staffList, 'study': study})
The Template:
<form action="{% url 'studies:addstaff' study.slug %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="box-body">
{% for list in staffList %}
<div class="form-group" id="formset">
{% if list.instance.pk %}{{ list.DELETE }}{% endif %}
{{ list.staff }}
{% if list.staff.errors %}
{% for error in list.staff.errors %}
{{ error|escape }}
{% endfor %}
{% endif %}
</div>
{% endfor %}
{{ staffList.management_form }}
</div>
<div class="box-footer">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
You are not including the primary key field in the template, as required by the docs. Add
{% for list in staffList %}
{{ list.pk }}
...
{% endfor %}
I made a loop allowing me to display for each page questions and answers utilisers.
I wonder if it's possible in the template to "filter" to get only the issues of page 1 for example?
<form action="" method="GET">
{{ form.as_p }}
{% for page in pages %}<hr>
{{ page }}:
<br>{% for reply in page.reply_set.all %}<br> {{ reply.question }} --> {{ reply.answer }} (Author : {{ reply.user }}) {% endfor %}
{% endfor %}
I have a fields ManytoMany so this is what it's hard to understand...
class Question(models.Model):
label = models.CharField(max_length=30)
def __str__(self):
return self.label
class Page(models.Model):
title = models.CharField(max_length=30)
def __str__(self):
return self.title
class Reply(models.Model):
page = models.ManyToManyField(Page)
question = models.ForeignKey(Question)
user = models.ForeignKey(Personne)
answer = models.CharField(max_length=30)
creationDate = models.DateTimeField(default=timezone.now(),blank=True, verbose_name="Date de création")
def __str__(self):
return str(self.answer)
You can see on this screenshot I have tree pages('name visit 1 visit 2 visit 3) and i want just the first visit what is the loop for get this ?
The best I would get is the page that I want based on the id
EDIT : views.py
def access(request,instance):
replies = Reply.objects.all()
questions = Question.objects.values()
logged_user = get_logged_user_from_request(request)
numPages = Page.objects.get(pk=instance)
pages = Page.objects.all()
# pagesfilter = Page.objects.get(pk=instance).reply_set.all()
form = ReplyForm(request.GET)
personnes = Personne.objects.all()
if logged_user:
if len(request.GET) > 0:
form = ReplyForm(request.GET)
if form.is_valid():
form.save(commit=True)
return HttpResponseRedirect('/reply')
else:
return render_to_response('polls/access.html', {'pagesfilter':pagesfilter, 'numPages': numPages, 'personnes': personnes, 'replies': replies, 'questions': questions,'pages':pages, 'form': form})
else:
form = ReplyForm()
return render_to_response('polls/access.html', {'pagesfilter':pagesfilter, 'numPages': numPages, 'personnes':personnes, 'replies': replies, 'questions': questions, 'pages':pages, 'form': form})
else:
return HttpResponseRedirect('/login')
If you know visit1 is the first element in the loop you can do:
{% for page in pages %}<hr>
{% if forloop.first %}
{{ page }}:
<br>{% for reply in page.reply_set.all %}<br> {{ reply.question }} --> {{ reply.answer }} (Author : {{ reply.user }}) {% endfor %}
{% endif %}
{% endfor %}
But the best way, I think, is to get visit1 in your view through a query and send it then to the template.
EDIT
As your view looks
numPages = Page.objects.get(pk=instance)
gets the Page you want. So, in template you could use it:
{{ numPage }}
{% for reply in numPage.reply_set.all %}
{{ reply.question }}--> {{ reply.answer }}(Author : {{ reply.user }})
{% endfor %}
Please have a look at this code:
models:
class Activity(models.Model):
actor = models.ForeignKey(User)
action = models.CharField(max_length=100)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
pub_date = models.DateTimeField(auto_now_add=True, auto_now=False)
class Meta:
verbose_name = 'Activity'
verbose_name_plural = 'Activities'
ordering = ['-pub_date']
def __unicode__(self):
return ("%s %s") % (self.actor.username, self.action)
def get_rendered_html(self):
template_name = '%s_activity.html' %(self.content_type.name)
return render_to_string(template_name, {
'object':self.content_object,
'actor':self.actor,
'action':self.action,
})
template:
<div class="user_activity">
<p>{{ actor.username }} {{ action }} {{ object.content_object.user.username }} status</p>
<p>{{ object.content_object.body }}</p>
<p>{{ object.content_object.pub_date }}</p>
{% if object.content_object.image %}
<div class="activity_img_wrapper">
<p><img src="/media/{{ object.content_object.image }}"/></p>
</div>
{% endif %}
</div>
Question
How do I get the requested user's username for the above template (request.user). I did like this, but it didn't help :
<div class="user_activity">
<p>
{% if user.username == actor.username %}
You
{% else %}
{{ actor.username }}
{% endif %}
{{ action }}
{% if user.username == object.content_object.user.username %}
Your
{% else %}
{{ object.content_object.user.username }}
{% endif %}
status
</p>
<p>{{ object.content_object.body }}</p>
<p>{{ object.content_object.pub_date }}</p>
{% if object.content_object.image %}
<div class="activity_img_wrapper">
<p><img src="/media/{{ object.content_object.image }}"/></p>
</div>
{% endif %}
</div>
Please help me how to do it. I would really be grateful for your help. Thank you.
There is no RequestContext object available in the get_rendered_html() method so you can't pass it as a context_instance argument of the render_to_string(). This is why the user variable is not available in the template.
You should pass the User instance to get_rendered_html() method and propagate it to the template:
def get_rendered_html(self, user=None):
template_name = '%s_activity.html' %(self.content_type.name)
return render_to_string(template_name, {
'object':self.content_object,
'actor':self.actor,
'action':self.action,
'user':user,
})
If you want to call this method from other template then the best option is to use custom template tag:
# app/templatetags/activity_tags.py
# and don't forget to create empty app/templatetags/__init__.py :-)
from django import template
from django.utils.safestring import mark_safe
register = template.Library()
#register.simple_tag(takes_context=True)
def render_activity(context, activity):
user = context['user']
html = activity.get_rendered_html(user)
return mark_safe(html)
And then load and use this tag library in your template:
{% load activity_tags %}
...
{% render_activity activity %}
I am having difficulties with forms, specifically ModelMultipleChoiceField.
I've pieced together this code from various examples, but it sadly doesn't work.
I would like to be able to:
Search for some Works on work_search.html
Display the results of the search, with checkboxes next to each result
Select the Works I want, via the checkboxes
After pressing Add, display which works were selected.
I believe everything is okay except the last part. The page simply displays "works" :(
Here is the code - sorry about the length.
Models.py
class Work(models.Model):
title = models.CharField(max_length=200)
artist = models.CharField(max_length=200)
writers = models.CharField(max_length=200)
def __unicode__(self):
return self.title + ' - ' + self.artist
forms.py
class WorkSelectForm(forms.Form):
def __init__(self, queryset, *args, **kwargs):
super(WorkSelectForm, self).__init__(*args, **kwargs)
self.fields['works'] = forms.ModelMultipleChoiceField(queryset=queryset, widget=forms.CheckboxSelectMultiple())
views.py
def work_search(request):
query = request.GET.get('q', '')
if query:
qset = (
Q(title__icontains=query) |
Q(artist__icontains=query) |
Q(writers__icontains=query)
)
results = Work.objects.filter(qset).distinct()
form = WorkSelectForm(results)
return render_to_response("work_search.html", {"form": form, "query": query })
else:
results = []
return render_to_response("work_search.html", {"query": query })
def add_works(request):
#if request.method == POST:
form = WorkSelectForm(request.POST)
#if form.isvalid():
items = form.fields['works'].queryset
return render_to_response("add_works.html", {"items":items})
work_search.html
{% extends "base.html" %}
{% block content %}
<h1>Search</h1>
<form action="." method="GET">
<label for="q">Search: </label>
<input type="text" name="q" value="{{ query|escape }}">
<input type="submit" value="Search">
</form>
{% if query %}
<h2>Results for "{{ query|escape }}":</h2>
<form action="add_works" method="post">
<ul>
{% if form %}
{{ form.as_ul }}
{% endif %}
</ul>
<input type="submit" value="Add">
</form>
{% endif %}
{% endblock %}
add_works.html
{% extends "base.html" %}
{% block content %}
{% if items %}
{% for item in items %}
{{ item }}
{% endfor %}
{% else %}
<p>Nothing selected</p>
{% endif %}
{% endblock %}
In add_works, you're not constructing your WorkSelectForm the right way. It's expecting as a first parameter the queryset of possible/authorized choices, then the POST data.
Also, you're not accessing the selected works correctly from the form. You have to use is_valid method on the form, then use cleaned_data as described in the doc.
From what I see in your work_search view, there's no restriction on which Work objects you can search then add to the result, so you could do simply:
def add_works(request):
#if request.method == POST:
form = WorkSelectForm(Work.objects.all(), request.POST)
if form.is_valid():
# the items are in form.cleaned_data['works']
items = form.cleaned_data['works']
return render_to_response("add_works.html", {"items":items})
else:
# handle error case here
...