Part 2 – Designing the recipes app (posts)
Picking up where we left off: In Part 1 we set up our Python 3.5 environment, installed Django 1.10 and generated a new project. We even ran the development server to make sure everything worked. Now that the groundwork is in place, it’s time to build the heart of Foodiegram – an app to store and display recipes.
Django projects are made up of apps. An app is a component that does one thing: a blog, a forum, a todo list. Foodiegram’s recipes will live in an app we’ll call posts.
From the project root, create the app:
python manage.py startapp posts
This command creates a posts
directory with files like models.py
, views.py
and admin.py
. Each file has a purpose: models describe your data, views contain business logic and controllers, and admin handles the administration interface.
Registering the app and configuring static/media files
Open foodiegram/settings.py
and find the INSTALLED_APPS
list. Add 'posts'
so Django knows about your app:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'posts', # our recipes app
]
Django’s static files system serves CSS, JavaScript and images during development. At the bottom of settings.py
, add definitions for static and media storage:
# Static files configuration
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
# Media files configuration
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
STATIC_ROOT
is used when collecting static files for deployment. MEDIA_ROOT
tells Django where user‑uploaded files (e.g. recipe photos) live, and MEDIA_URL
is the URL prefix used to serve those files.
Building the Post model
Our recipes are called posts. Open posts/models.py
and define a Post
model that captures the information we need. The original Foodiegram tutorial used fields for title, slug, image, description, ingredients, publication date, view count, like count and author. Here’s a slightly simplified version:
from django.db import models
from django.utils.text import slugify
from django.contrib.auth.models import User
class Post(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
image = models.ImageField(upload_to='posts/')
description = models.TextField()
ingredients = models.TextField()
pub_date = models.DateTimeField(auto_now_add=True)
views = models.PositiveIntegerField(default=0)
likes = models.PositiveIntegerField(default=0)
author = models.ForeignKey(User, on_delete=models.CASCADE)
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.title)
super().save(*args, **kwargs)
def __str__(self):
return self.title
class Meta:
ordering = ['-pub_date']
The slug
field provides human‑readable URLs. ImageField
stores an uploaded image in the media/posts/
directory. auto_now_add=True
automatically timestamps new posts. The Meta
class sorts posts in reverse chronological order.
You’ll need the Pillow library for image processing. Install it within your environment and record it in requirements.txt
:
pip install Pillow
pip freeze > requirements.txt
Run migrations to create the database tables:
python manage.py makemigrations
python manage.py migrate
Writing basic views
Next, create the first two views in posts/views.py
. One view lists all recipes and another displays a single recipe. In the original tutorial post_list
queried all posts and post_detail
used get_object_or_404
to fetch a single post by its slug. Here’s the code:
from django.shortcuts import render, get_object_or_404
from .models import Post
def post_list(request):
posts = Post.objects.all()
return render(request, 'posts/post_list.html', {'posts': posts})
def post_detail(request, slug):
post = get_object_or_404(Post, slug=slug)
# Increment view counter
post.views += 1
post.save()
return render(request, 'posts/post_detail.html', {'post': post})
Wiring up URLs
Create posts/urls.py
and map URL patterns to the views. Django 1.10 still uses the url()
function from django.conf.urls
as shown in Foodiegram:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.post_list, name='post_list'),
url(r'^post/(?P<slug>[-\w]+)/$', views.post_detail, name='post_detail'),
]
Then include this app’s URLs in the project’s root foodiegram/urls.py
. This file already contains the admin route; modify it to include posts.urls
and serve media files in development:
from django.conf.urls import url, include
from django.contrib import admin
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^', include('posts.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
What’s next?
Restart the development server, navigate to http://127.0.0.1:8000/
and you’ll see an empty page because we haven’t created any templates yet. The next section tackles styling and templates so your recipes actually appear.