Welcome to the Treehouse Community

The Treehouse Community is a meeting place for developers, designers, and programmers of all backgrounds and skill levels to get support. Collaborate here on code errors or bugs that you need feedback on, or asking for an extra set of eyes on your latest project. Join thousands of Treehouse students and alumni in the community today. (Note: Only Treehouse students can comment or ask questions, but non-students are welcome to browse our conversations.)

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and a supportive community. Start your free trial today.

Python Django Forms Inlines and Media Create an Inline Model Formset

Inline formsets - the outer edge of arcane Django trivia

I'm glad there is only one challenge dealing with inline (nested?) formsets.

Unfortunately it has two parts and I can't even imagine what they want for part one.


So I watched the video dozens of times --especially the part where Instructor Love admits that even he has had few occasions to use Django inline formsets...

rollseyes-emoji


So then I dug through the zip downloads to look over the code presented in the video directly.

.

In the views.py file (under Final\Learning Site\Courses) is the code that

somehow might provide a clue as what is the needed code,

but looking at said code I still have no idea how to translate this code's 'pattern'

into what the challenge might be looking for ):

from itertools import chain

from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render

from . import forms
from . import models


def course_list(request):
    courses = models.Course.objects.all()
    return render(request, 'courses/course_list.html', {'courses': courses})


def course_detail(request, pk):
    course = get_object_or_404(models.Course, pk=pk)
    steps = sorted(chain(course.text_set.all(), course.quiz_set.all()),
                   key=lambda step: step.order)
    return render(request, 'courses/course_detail.html', {
            'course': course,
            'steps': steps
        })


def text_detail(request, course_pk, step_pk):
    step = get_object_or_404(models.Text, course_id=course_pk, pk=step_pk)
    return render(request, 'courses/text_detail.html', {'step': step})


def quiz_detail(request, course_pk, step_pk):
    step = get_object_or_404(models.Quiz, course_id=course_pk, pk=step_pk)
    return render(request, 'courses/quiz_detail.html', {'step': step})


@login_required
def quiz_create(request, course_pk):
    course = get_object_or_404(models.Course, pk=course_pk)
    form = forms.QuizForm()

    if request.method == 'POST':
        form = forms.QuizForm(request.POST)
        if form.is_valid():
            quiz = form.save(commit=False)
            quiz.course = course
            quiz.save()
            messages.add_message(request, messages.SUCCESS,
                                 "Quiz added!")
            return HttpResponseRedirect(quiz.get_absolute_url())
    return render(request, 'courses/quiz_form.html', {'form': form, 'course': course})


@login_required
def quiz_edit(request, course_pk, quiz_pk):
    quiz = get_object_or_404(models.Quiz, pk=quiz_pk, course_id=course_pk)
    form = forms.QuizForm(instance=quiz)

    if request.method == 'POST':
        form = forms.QuizForm(instance=quiz, data=request.POST)
        if form.is_valid():
            form.save()
            messages.success(request, "Updated {}".format(form.cleaned_data['title']))
            return HttpResponseRedirect(quiz.get_absolute_url())
    return render(request, 'courses/quiz_form.html', {'form': form, 'course': quiz.course})


@login_required
def create_question(request, quiz_pk, question_type):
    quiz = get_object_or_404(models.Quiz, pk=quiz_pk)
    if question_type == 'tf':
        form_class = forms.TrueFalseQuestionForm
    else:
        form_class = forms.MultipleChoiceQuestionForm

    form = form_class()
    answer_forms = forms.AnswerInlineFormSet(
        queryset=models.Answer.objects.none()
    )

    if request.method == 'POST':
        form = form_class(request.POST)
        answer_forms = forms.AnswerInlineFormSet(
            request.POST,
            queryset=models.Answer.objects.non()
        )

        if form.is_valid() and answer_forms.is_valid():
            question = form.save(commit=False)
            question.quiz = quiz
            question.save()
            answers = answer_forms.save(commit=False)
            for answer in answers:
                answer.question = question
                answer.save()
            messages.success(request, "Added question")
            return HttpResponseRedirect(quiz.get_absolute_url())

    return render(request, 'courses/question_form.html', {
            'quiz': quiz,
            'form': form,
            'formset': answer_forms
    })


@login_required
def edit_question(request, quiz_pk, question_pk):
    question = get_object_or_404(models.Question,
                                 pk=question_pk, quiz_id=quiz_pk)
    if hasattr(question, 'truefalsequestion'):
        form_class = forms.TrueFalseQuestionForm
        question = question.truefalsequestion
    else:
        form_class = forms.MultipleChoiceQuestionForm
        question = question.multiplechoicequestion
    form = form_class(instance=question)
    answer_forms = forms.AnswerInlineFormSet(
        queryset=form.instance.answer_set.all()
    )

    if request.method == 'POST':
        form = form_class(request.POST, instance=question)
        answer_forms = forms.AnswerInlineFormSet(
            request.POST,
            queryset=form.instance.answer_set.all()
        )
        if form.is_valid() and answer_forms.is_valid():
            form.save()
            answers = answer_forms.save(commit=False)
            for answer in answers:
                answer.question = question
                answer.save()
            for answer in answer_forms.deleted_objects:
                answer.delete()
            messages.success(request, "Updated question")
            return HttpResponseRedirect(question.quiz.get_absolute_url())
    return render(request, 'courses/question_form.html', {
            'form': form,
            'quiz': question.quiz,
            'formset': answer_forms
    })


@login_required
def answer_form(request, question_pk, answer_pk=None):
    question = get_object_or_404(models.Question, pk=question_pk)
    formset = forms.AnswerFormSet(queryset=question.answer_set.all())

    if request.method == 'POST':
        formset = forms.AnswerFormSet(request.POST,
                                      queryset=question.answer_set.all())

        if formset.is_valid():
            answers = formset.save(commit=False)

            for answer in answers:
                answer.question = question
                answer.save()
            messages.success(request, "Added answers")
            return HttpResponseRedirect(question.quiz.get_absolute_url())
    return render(request, 'courses/answer_form.html', {
            'formset': formset,
            'question': question
    })

Since Instructor Love's video was in no way helpful in understanding the use of inline formsets

I did a little googling around, but didn't find much:

http://kevindias.com/writing/django-class-based-views-multiple-inline-formsets/

http://charlesleifer.com/blog/djangos-inlineformsetfactory-and-you/

https://docs.djangoproject.com/en/1.9/topics/forms/modelforms/#inline-formsets

https://docs.djangoproject.com/en/1.9/topics/forms/modelforms/#overriding-methods-on-an-inlineformset

https://gist.github.com/neara/6209563

http://stackoverflow.com/questions/1113047/creating-a-model-and-related-models-with-inline-formsets

http://stackoverflow.com/questions/1913913/django-inlineformsetfactory-what-is-it-good-for

http://stackoverflow.com/questions/11976563/django-inline-formset-example-from-documentation

https://djangosnippets.org/snippets/1246/

https://code.google.com/p/django-dynamic-formset/wiki/Usage#Working_with_inline_formsets

Maybe someone looking at the above links can piece together some code that the challenge

might like, but I'm totally lost...


By the way I use the word "arcane" in technical contexts to describe anything so obscure and

uncommon that I am unlikely to actually use it in real life.

I know the word has other more non-technically related meanings to the A.D. &D/RPG crowds..

1 Answer

Chris Freeman
MOD
Chris Freeman
Treehouse Moderator 67,983 Points

Hi James, lets walk through a solution.

The challenge asks:

I want to be able to create a Review in the same form as where I create a Product. That means I need an inline form! Create an inline model formset factory, named ReviewFormset, for the Review model. You need to include all the same fields as the existing ReviewForm. Remember, the first argument to the factory is the parent model (Product) and the second is the model the factory is for (Review).

At 5:40 in the video, Kenneth describes how this would be coded.

# the given code
from django import forms

from . import models
from products.models import Product


class ReviewForm(forms.ModelForm):

    class Meta:
        model = models.Review
        fields = ('headline', 'rating', 'content', 'writer', 'publish_date')

# new code here
# I want to be able to create a Review in the same form as where I
# create a Product. That means I need an inline form!


# Step 1: Create an inline model formset factory, named ReviewFormset, for the
# Review model.
ReviewFormset = forms.inlineformset_factory()

# Step2: Remember, the first argument to the factory is the parent model(Product)
# and the second is the model the factory is for (Review).
ReviewFormset = forms.inlineformset_factory(
    Product,
    models.Review
)

# Step 3: You need to include all the same fields as the existing ReviewForm.
ReviewFormset = forms.inlineformset_factory(
    Product,
    models.Review,
    fields=('headline', 'rating', 'content', 'writer', 'publish_date')
)

Task 2 says: Great! By default, I get 3 extra forms. That's a lot for a single view since they're big forms. Can you change it so I only get 1 extra?

ReviewFormset = forms.inlineformset_factory(
    Product,
    models.Review,
    fields=('headline', 'rating', 'content', 'writer', 'publish_date'),
    # Step 1: Change it so I only get 1 extra
    extra=1
)