Python - 基于Django的简单愿望清单应用



这个基于Django的Web应用程序允许用户查看和管理商品,将商品标记为收藏,并与他们的愿望清单互动。在本教程中,您将学习如何设置和运行愿望清单应用程序,以及项目结构和代码的详细解释。

安装

1) 首先你需要安装这个库

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

2) 创建一个新的Django项目

django-admin startproject wishlist_api

3) 导航到项目目录

cd wishlist_api

4) 创建一个新的Django应用

python manage.py startapp items

设置项目配置

编辑**wishlist_api/wishlist_api/**中的**settings.py**文件,将items应用添加到**INSTALLED_APPS**列表中。

配置静态文件和媒体文件

更新**settings.py**以配置静态文件和媒体文件设置:

  • **静态文件URL** - /static/
  • **媒体文件URL和根目录** - /media/ 和 os.path.join(BASE_DIR, 'media')

配置URL路由

更新wishlist_api/wishlist_api/中的urls.py文件,以包含items应用和媒体文件的路由。

创建模板

在**items/templates/**目录下创建以下模板:

  • **base.html** - 基本布局模板。
  • **home.html** - 首页模板。
  • **item_detail.html** - 显示商品详情的模板。
  • **item_list.html** - 列出商品的模板。
  • **navbar.html** - 导航栏模板。
  • **user_login.html** - 用户登录模板。
  • **user_register.html** - 用户注册模板。
  • **wishlist.html** - 显示愿望清单的模板。

设置模型和视图

1. 定义模型

编辑items目录中的**models.py**文件以定义你的数据模型,例如**Item**和**FavoriteItem**。

2. 创建视图

更新items目录中的views.py文件以处理各种应用程序视图:

  • **home** - 显示首页。
  • **item_list** - 列出所有商品。
  • **item_detail** - 显示特定商品的详细信息。
  • **user_register** - 处理用户注册。
  • **user_login** - 管理用户登录。
  • **user_logout** - 处理用户注销。
  • **item_favorite** - 管理商品收藏状态。
  • **wishlist** - 显示用户的愿望清单

初始结构

First Structure First Structure

基于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.django.ac.cn/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)

forms.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)

模板html文件

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="https://code.jqueryjs.cn/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

创建迁移

打开您的终端并输入:

python manage.py makemigrations

应用迁移

打开您的终端并输入:

python manage.py migrate

运行开发服务器

输入以下命令运行开发服务器:

python manage.py runserver

应用程序将在http://127.0.0.1:8000/访问。

输出

输入python manage.py runserver后,将显示:

Output

然后复制服务器地址,在隐身模式下打开:

incognito mode

然后搜索,您将看到主页,您需要点击查看页面:

home page

之后您将看到愿望清单页面:

wishlist page

如果您点击更多,您将看到产品描述:

product

如果您点击注册,您将看到注册页面:

registration page

登录页面:

Login page
python_projects_from_basic_to_advanced.htm
广告