set initial value in CreateView from ForeignKey (non-self.request.user)

Jeremiah picture Jeremiah · Aug 16, 2013 · Viewed 14k times · Source

I am attempting to access ForeignKeys in Class Based Views CreateView. I would like to be able to dynamically set initial values in CBV from ForeignKeys and also dynamically set template links from ForeignKeys.

These two questions (1. initial values, 2. template links) may be solved in similar methods, or perhaps by different methods... I'm still learning. Perhaps the first question can be solved within views.py and the second question can be solved with template syntax in ingredient_form.html?

I've seen questions on SO setting initial values from users (self.request.user), but not from just a normal foreign key in models.py.

I'm going through django-by-errors, and attempting to add extra features to expand my django knowledge.

My question specifically centers on views.py:IngredientAddView(CreateView), on ingredient_form.html, and on urls.py:'recipe-detail' & 'ingredient-add'.

When I view a 'recipe-detail', I can click a link to 'ingredient-add'. I would like 'ingredient-add' to "know" which recipe clicked to it, and be able to set this recipe as the initial value (my attempt within views.py:IngredientAddView:get_initials(self) does not work), and also be able to link back to this recipe (my attempt within ingredient_form.html:{% comment %} does not work).

Would appreciate any assistance.

models.py

class Food(models.Model):
    name=models.CharField(max_length=20,unique=True)

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse('food-detail',kwargs={'pk':self.pk})

class Recipe(models.Model):
    title=models.CharField(max_length=80,unique=True)
    slug=models.SlugField(max_length=80,unique=True)
    description=models.TextField(blank=True)

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse('recipe-detail',kwargs={'slug':self.slug})

class Ingredient(models.Model):
    recipe=models.ForeignKey(Recipe)
    food=models.ForeignKey(Food)

    def __str__(self):
        return '%s (%s)' % (self.food, self.recipe)

views.py

class FoodListView(ListView):
    model=Food

class FoodDetailView(DetailView):
    model=Food

class FoodCreateView(CreateView):
    model=Food

class RecipeListView(ListView):
    model=Recipe

class RecipeDetailView(DetailView):
    model=Recipe

class RecipeCreateView(CreateView):
    model=Recipe

class RecipeUpdateView(UpdateView):
    model=Recipe

class IngredientAddView(CreateView):
    model=Ingredient

#    def get_context_data(self,**kwargs):
#        context=super(IngredientAddView,self).get_context_data(**kwargs)
#        context['foreign']=self.request.session.get('slug')

    def get_initials(self):
        return {
            'recipe':self.request.session.get('recipe')
        }

urls.py

from .views import FoodListView, FoodDetailView, FoodCreateView, RecipeListView, RecipeDetailView, RecipeCreateView, RecipeUpdateView, IngredientAddView

urlpatterns=patterns('',
                     url(r'^$',RecipeListView.as_view(),name='recipe-list'),
                     url(r'^(?P<slug>[-\w]+)$',RecipeDetailView.as_view(),name='recipe-detail'),
                     url(r'^(?P<slug>[-\w]+)/edit$',RecipeUpdateView.as_view(),name='recipe-edit'),
                     url(r'^(?P<slug>[-\w]+)/add_ingredient/$',IngredientAddView.as_view(),name='ingredient-add'),
                     url(r'^new/$',RecipeCreateView.as_view(),name='recipe-create'),
                     url(r'^food/$',FoodListView.as_view(),name='food-list'),
                     url(r'^food/(?P<pk>[\d]+)$',FoodDetailView.as_view(),name='food-detail'),
                     url(r'^food/create/$',FoodCreateView.as_view(),name='food-create'),
                 )

recipe_detail.html

{% extends "base_food.html" %}

{% block title %}{{ recipe }} {% endblock %}

{% block content %}
  <h1>{{ recipe }}</h1>
  <p>{{ recipe.id }}</p>
  <p>{{ recipe.title }}</p>

  <br>
    <h2>Description</h2>
  <p>{{ recipe.description|default:'No description' }}</p>

  <h2>Ingredients</h2>
  <ul>
    {% for ingredient in recipe.ingredient_set.all %}
      <li>{{ ingredient }}</li>
  {% endfor %}
  </ul>
  <p><a href="{% url 'ingredient-add' recipe.slug %}">Add ingredient</a></p>
  <p><a href="{% url 'recipe-edit' recipe.slug %}">Edit recipe</a></p>
  <p><a href="{% url 'recipe-list' %}">Back to recipe list</a></p>
{% endblock %}

ingredient_form.html

{% extends "base_food.html" %}

{% block title %}Add Ingredient{% endblock %}

{% block content %}
  <h1>Add Ingredient</h1>
  <form method="POST">{% csrf_token %}
    {{ form }}
    <button type="submit" class="btn btn-primary">Save</button>
  </form>

{%comment%}  <p><a href="{% url 'recipe-detail' recipe.slug %}">Back to detail</a></p> {%endcomment%}
  <p><a href="{% url 'recipe-list' %}">Back to recipe list</a></p>
{% endblock %}

Answer

Berislav Lopac picture Berislav Lopac · Aug 16, 2013

You need to instantiate your recipe:

class IngredientAddView(CreateView):
    model=Ingredient

    def get_initial(self):
        recipe = get_object_or_404(Recipe, slug=self.kwargs.get('slug'))
        return {
            'recipe':recipe,
        }