Python - Simple WishList App Using Django



This Django-based web application enables users to view and manage items, mark them as favorites, and interact with their wishlist. In this tutorial, you'll know how to set up and run the wishlist application, as well as detailed explanations of the project structure and code.

Installations

1) First you need it install this library

pip install Django==2.2.13,pip install pytz==2018.9, pip install Pillow

2) Create a New Django Project

django-admin startproject wishlist_api

3) Navigate into the project directory

cd wishlist_api

4) Create a New Django App

python manage.py startapp items

Set Up Project Configurations

Edit the settings.py file in wishlist_api/wishlist_api/ to add the items app to the INSTALLED_APPS list.

Configure Static and Media Files

Update settings.py to configure static and media file settings −

  • Static files URL − /static/
  • Media files URL and root − /media/ and os.path.join(BASE_DIR, 'media')

Configure URL Routing

Update the urls.py file in wishlist_api/wishlist_api/ to include routes for the items app and media files.

Create the Templates

Create the following templates in the items/templates/ directory

  • base.html − The base layout template.
  • home.html − The homepage template.
  • item_detail.html − Template for displaying item details.
  • item_list.html − Template for listing items.
  • navbar.html − Navigation bar template.
  • user_login.html − Template for user login.
  • user_register.html − Template for user registration.
  • wishlist.html − Template for displaying the wishlist.

Set Up Models and Views

1. Define Models

Edit models.py in the items directory to define your data models, such as Item and FavoriteItem.

2. Create Views

Update views.py in the items directory to handle various application views −

  • home − Displays the homepage.
  • item_list − Lists all items.
  • item_detail − Shows details for a specific item.
  • user_register − Handles user registration.
  • user_login − Manages user login.
  • user_logout − Handles user logout.
  • item_favorite − Manages item favorite status.
  • wishlist − Displays the user's wishlist

First Structure

First Structure First Structure

Code Files for Simple WishList App Using Django

Settings.py

import os

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

SECRET_KEY = os.getenv('DJANGO_SECRET_KEY', 'your-default-secret-key')

DEBUG = os.getenv('DJANGO_DEBUG', 'True') == 'True'

ALLOWED_HOSTS = ['yourdomain.com', 'localhost', '127.0.0.1']

INSTALLED_APPS = [
   'django.contrib.admin',
   'django.contrib.auth',
   'django.contrib.contenttypes',
   'django.contrib.sessions',
   'django.contrib.messages',
   'django.contrib.staticfiles',
   'items',
   # Add additional apps here
]

MIDDLEWARE = [
   'django.middleware.security.SecurityMiddleware',
   'django.contrib.sessions.middleware.SessionMiddleware',
   'django.middleware.common.CommonMiddleware',
   'django.middleware.csrf.CsrfViewMiddleware',
   'django.contrib.auth.middleware.AuthenticationMiddleware',
   'django.contrib.messages.middleware.MessageMiddleware',
   'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'wishlist_api.urls'

TEMPLATES = [
   {
      'BACKEND': 'django.template.backends.django.DjangoTemplates',
      'DIRS': [os.path.join(BASE_DIR, 'templates')],  # Ensure this line is present
      'APP_DIRS': True,
      'OPTIONS': {
         'context_processors': [
            'django.template.context_processors.debug',
            'django.template.context_processors.request',
            'django.contrib.auth.context_processors.auth',
            'django.contrib.messages.context_processors.messages',
         ],
      },
   },
]

WSGI_APPLICATION = 'wishlist_api.wsgi.application'

DATABASES = {
   'default': {
      'ENGINE': 'django.db.backends.sqlite3',
      'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
   }
}

AUTH_PASSWORD_VALIDATORS = [
   {
      'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
   },
   {
      'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
   },
   {
      'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
   },
   {
      'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
   },
]

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True

STATIC_URL = '/static/'

MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

Urls.py

# wishlist_api/urls.py

from django.contrib import admin
from django.urls import path
from items import views
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
   path('', views.home, name='home'),  # Add this line
   path('admin/', admin.site.urls),
   path('items/list/', views.item_list, name='item-list'),
   path('items/detail/<int:item_id>/', views.item_detail, name='item-detail'),
   path('items/wishlist/', views.wishlist, name='wishlist'),
   path('user/register/', views.user_register, name='user-register'),
   path('user/login/', views.user_login, name='user-login'),
   path('user/logout/', views.user_logout, name='user-logout'),
   path('items/<int:item_id>/favorite/', views.item_favorite, name='item-favorite'),
]

urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

wsgi.py

"""
WSGI config for wishlist_api project.

It exposes the WSGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/
"""

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "wishlist_api.settings")

application = get_wsgi_application()

views.py

from django.shortcuts import render, redirect
from items.models import Item, FavoriteItem
from .forms import UserRegisterForm, UserLoginForm
from django.contrib.auth import login, logout, authenticate
from django.http import JsonResponse

def home(request):
   return render(request, 'home.html')

def item_list(request):
   items = Item.objects.all()
   query = request.GET.get('q')
   if query:
      items = items.filter(name__icontains=query)

   favorite_list = []
   if request.user.is_authenticated:
      favorite_list = request.user.favoriteitem_set.values_list('item', flat=True)

   context = {
      "items": items,
      "favorite_list": favorite_list
   }
   return render(request, 'item_list.html', context)

def item_detail(request, item_id):
   context = {
      "item": Item.objects.get(id=item_id)
   }
   return render(request, 'item_detail.html', context)

def user_register(request):
   register_form = UserRegisterForm()
   if request.method == "POST":
      register_form = UserRegisterForm(request.POST)
      if register_form.is_valid():
         user = register_form.save(commit=False)
         user.set_password(register_form.cleaned_data['password'])
         user.save()
         login(request, user)
         return redirect('item-list')
   context = {
      "register_form": register_form
   }
   return render(request, 'user_register.html', context)

def user_login(request):
   login_form = UserLoginForm()
   if request.method == "POST":
      login_form = UserLoginForm(request.POST)
      if login_form.is_valid():
         username = login_form.cleaned_data['username']
         password = login_form.cleaned_data['password']
         authenticated_user = authenticate(username=username, password=password)
         if authenticated_user:
            login(request, authenticated_user)
            return redirect('item-list')
   context = {
      "login_form": login_form
   }
   return render(request, 'user_login.html', context)

def user_logout(request):
   logout(request)
   return redirect('item-list')

def item_favorite(request, item_id):
   if request.user.is_anonymous:
      return redirect('user-login')

   item_object = Item.objects.get(id=item_id)
   favorite, created = FavoriteItem.objects.get_or_create(user=request.user, item=item_object)

   if created:
      action = "favorite"
   else:
      favorite.delete()
      action = "unfavorite"

   response = {
      "action": action,
   }
   return JsonResponse(response, safe=False)

def wishlist(request):
   items = Item.objects.all()
   query = request.GET.get('q')
   if query:
      items = items.filter(name__icontains=query)

   favorite_objects = []
   if request.user.is_authenticated:
      favorite_objects = request.user.favoriteitem_set.all()

   wishlist = [item for item in items if any(item.id == fav.item_id for fav in favorite_objects)]

   context = {
      "wishlist": wishlist
   }
   return render(request, 'wishlist.html', context)

tests.py

from django.test import TestCase
from django.urls import reverse
from django.contrib.auth.models import User
from rest_framework.test import APITestCase
from rest_framework import status
from datetime import date

from items.models import Item, FavoriteItem

class ItemListViewTest(APITestCase):
   def setUp(self):
      user = User.objects.create_user(username="laila", password="1234567890-=")
      self.item1 = {'image': 'foo.jpg', 'name': "yaaay", 'description': "yay object", 'added_by': user}
      self.item2 = {'image': 'foo.jpg', 'name':"booo" , 'description': "boo object", 'added_by': user}
      Item.objects.create(**self.item1)
      Item.objects.create(**self.item2)

   def test_url_works(self):
      response = self.client.get(reverse('api-list'))
      self.assertEqual(response.status_code, status.HTTP_200_OK)

   def test_list(self):
      response = self.client.get(reverse('api-list'))
      items = Item.objects.all()
      self.assertEqual(len(response.data), items.count())
      for index, item in enumerate(items):
         item = items[index]
         self.assertEqual(dict(response.data[index])['name'], item.name)

   def test_search(self):
      response = self.client.get(reverse('api-list'), {'search': 'y'})
      items = Item.objects.filter(name__icontains="y")
      self.assertEqual(len(response.data), items.count())
      for index, item in enumerate(items):
         item = items[index]
         self.assertEqual(dict(response.data[index])['name'], item.name)

   def test_ordering(self):
      response = self.client.get(reverse('api-list'), {'ordering': 'name'})
      items = Item.objects.order_by("name")
      self.assertEqual(len(response.data), items.count())
      for index, item in enumerate(items):
         item = items[index]
         self.assertEqual(dict(response.data[index])['name'], item.name)

   def test_details_url(self):
      response = self.client.get(reverse('api-list'))
      items = Item.objects.all()
      self.assertEqual(len(response.data), items.count())
      for index, item in enumerate(items):
         item = items[index]
         self.assertTrue(reverse('api-detail', args=[item.id]) in dict(response.data[index])['detail'])

   def test_user_serailized(self):
      response = self.client.get(reverse('api-list'))
      items = Item.objects.all()
      for index, item in enumerate(items):
         item = items[index]
         self.assertEqual(dict(response.data[index])['added_by'], {"first_name": item.added_by.first_name, "last_name": item.added_by.last_name})

   def test_favourited_field(self):
      response = self.client.get(reverse('api-list'))
      items = Item.objects.all()
      for index, item in enumerate(items):
         item = items[index]
         count = FavoriteItem.objects.filter(item=item).count()
         self.assertEqual(dict(response.data[index])['favourited'], count)

class ItemDetailViewTest(APITestCase):
   def setUp(self):
      user1 = User.objects.create_user(username="laila", password="1234567890-=")
      user2 = User.objects.create_user(username="laila2", password="1234567890-=")
      self.item1 = {'image': 'foo.jpg', 'name': "yaaay", 'description': "yay object", 'added_by': user1}
      self.item2 = {'image': 'foo.jpg', 'name':"booo" , 'description': "boo object", 'added_by': user2}
      Item.objects.create(**self.item1)
      Item.objects.create(**self.item2)

   def test_url_authorized(self):
      response = self.client.post(reverse('api-login'), {"username" : "laila", "password": "1234567890-="})
      self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + response.data['access'])
      response = self.client.get(reverse('api-detail', args=[1]))
      self.assertEqual(response.status_code, status.HTTP_200_OK)

   def test_url_unauthorized(self):
      response = self.client.post(reverse('api-login'), {"username" : "laila2", "password": "1234567890-="})
      self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + response.data['access'])
      response = self.client.get(reverse('api-detail', args=[1]))
      self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

   def test_details(self):
      response = self.client.post(reverse('api-login'), {"username" : "laila", "password": "1234567890-="})
      self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + response.data['access'])
      response = self.client.get(reverse('api-detail', args=[1]))
      self.assertEqual(dict(response.data)['name'], self.item1['name'])

   def test_users_sent(self):
      response = self.client.post(reverse('api-login'), {"username" : "laila", "password": "1234567890-="})
      self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + response.data['access'])
      response = self.client.get(reverse('api-detail', args=[1]))
      people_count = FavoriteItem.objects.filter(item_id=1).count()
      self.assertEqual(len(dict(response.data)['favourited_by']), people_count)

models.py

from django.db import models
from django.contrib.auth.models import User

class Item(models.Model):
   image = models.ImageField()
   name = models.CharField(max_length=120)
   description = models.TextField(max_length=255)

   def __str__(self):
      return self.name

class FavoriteItem(models.Model):
   item = models.ForeignKey(Item, on_delete=models.CASCADE)
   user = models.ForeignKey(User, on_delete=models.CASCADE)

froms.py

from django import forms
from django.contrib.auth.models import User

class UserRegisterForm(forms.ModelForm):
   password = forms.CharField(widget=forms.PasswordInput(attrs={'placeholder': 'Password'}))

   class Meta:
      model = User
      fields = ['username', 'first_name', 'last_name', 'password']

class UserLoginForm(forms.Form):
   username = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'Username'}))
   password = forms.CharField(widget=forms.PasswordInput(attrs={'placeholder': 'Password'}))

apps.py

from django.apps import AppConfig

class ItemsConfig(AppConfig):
   name = 'items'

admin.py

from django.contrib import admin
from .models import Item

# Register your models here.
admin.site.register(Item)

Templates html file

base.html

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<link
   rel="stylesheet"
   href="https://use.fontawesome.com/releases/v5.0.10/css/all.css"
   integrity="sha384-+d0P83n9kaQMCwj8F4RJB66tzIwOKmrdb46+porD/OvrJ+37WqIM7UoBtwHO6Nlg"
   crossorigin="anonymous">
<title>Items</title>
</head>
<body>
{%include 'navbar.html' %}
<br>
<div class="container">
   {% block content %}

   {% endblock content %}
</div>
<script
   src="http://code.jquery.com/jquery-3.3.1.min.js"
   integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
   crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
</body>
</html>

home.html

<!-- items/templates/home.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Home</title>
</head>
<body>
   <h1>Welcome to the Items App!</h1>
   <a href="{% url 'item-list' %}">View Items</a>
</body>
</html>

Item_detail.html

{% extends 'base.html' %}

{% block content %}
<div class="card mb-3">
   <img class="card-img-top" height="700" src="{{item.image.url}}" alt="Card image cap">
   <div class="card-body">
      <h5 class="card-title">{{item.name}}</h5>
      <p class="card-text">{{item.description}}</p>
   </div>
</div>
{% endblock %}

Item_list.html

{% extends 'base.html' %}

{% block content %}
<form class="form-inline my-2 my-lg-0" action="{% url 'item-list' %}">
   <input class="form-control mr-sm-2" type="search" placeholder="Search Items" aria-label="Search" name="q"> 
   <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
</form>
<div class="row">
{% for item in items %}
<div class="col-sm-4 py-2">
   <div class="card h-100">
      <img class="card-img-top" height="55%" src="{{item.image.url}}" alt="Card image cap">
      <div class="card-body bg-light">
         <h3 class="card-title">{{item.name}}</h3>
         <a href="{% url 'item-detail' item.id %}" class="btn btn-outline-dark">More</a>
         <button class="btn btn-light" onclick="favorite_item({{item.id}})"><i id="star-{{item.id}}"
            class="fas fa-star {% if item.id in favorite_list %}text-warning{% endif %}"></i></button>
      </div>
   </div>
</div>
{% endfor %}
</div>

<script>
   function favorite_item(id){
      $.ajax(
         {
            type: "GET",
            url: "/items/" + id + "/favorite",
            error: function(){
               console.log('error');
            },
            success: function(data){
               console.log(data);
               var item_id = "#star-"+id;
               console.log(item_id)
               if(data.action === "favorite"){
                  console.log("the action is favorite")
                  $(item_id).addClass("text-warning");
               } else {
                  $(item_id).removeClass("text-warning");
               }
            },
         }
      );
   }
</script>
{% endblock%}

Navbar.html

<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
   <a class="navbar-brand" href="{% url 'item-list' %}">Items</a>
   <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
   </button>
   <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav mr-auto">
         {% if request.user.is_anonymous %}
         <li class="nav-item">
            <a class="nav-link" href="{% url 'user-register' %}">Register</a>
         </li>
         <li class="nav-item">
            <a class="nav-link" href="{% url 'user-login' %}">Login</a>
         </li>

         {% else %}
         <li class="nav-item">
            <a class="nav-link" href="{% url 'wishlist' %}">My Wishlist</></a>
         </li>
         <li class="nav-item">
            <span class="nav-link disabled" href="#">Welcome, {{request.user}}.</span>
         </li>
         <li class="nav-item">
            <a class="nav-link" href="{% url 'user-logout' %}">Logout</a>
         </li>
         {% endif %}
      </ul>
   </div>
</nav>

User_login.html

{% extends 'base.html' %}

{% block content %}
<form action="{% url 'user-login' %}" method="POST">
   {% csrf_token %}
   {{login_form.as_p}}
   <input type="submit" value="Login">
</form>
{% endblock %}

User_register.html

{% extends 'base.html' %}

{% block content %}
<form action="{% url 'user-register' %}" method="POST">
   {% csrf_token %}
   {{register_form.as_p}}
   <input type="submit" value="Register">
</form>
{% endblock %}

Wishlist.html

{% extends 'base.html' %}

{% block content %}
<form class="form-inline my-2 my-lg-0" action="{% url 'wishlist' %}">
   <input class="form-control mr-sm-2" type="search" placeholder="Search Items" aria-label="Search" name="q"> 
   <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
</form>
<div class="row">
{% for item in wishlist %}
<div class="col-sm-4 py-2">
   <div class="card h-100">
      <img class="card-img-top" height="55%" src="{{item.image.url}}" alt="Card image cap">
      <div class="card-body bg-light">
         <h3 class="card-title">{{item.name}}</h3>
         <a href="{% url 'item-detail' item.id %}" class="btn btn-outline-dark">More</a>
      </div>
   </div>
</div>
{% endfor %}
</div>
{% endblock %}

Media file

In media file we used these images, you can use any images.

Media file

Make Migrations

Open your terminal and write this −

python manage.py makemigrations

Apply Migrations

Open your terminal and write this −

python manage.py migrate

Run the Development Server

Write the following command to run the development server −

python manage.py runserver

The application will be available at http://127.0.0.1:8000/.

OUTPUT

After you write python manage.py runserver it will show −

Output

Then copy the server open this incognito mode −

incognito mode

Then search you will see page home page , you need to click view page −

home page

After that you will see wishlist page −

wishlist page

If you click more you will the product description −

product

If you click register you will see registration page −

registration page

Login page −

Login page
python_projects_from_basic_to_advanced.htm
Advertisements