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.