18-2 简短的条目 :当前, Django 在管理网站或 shell 中显示 Entry 实例时,模型Entry 的方法 __str__() 都在它的末尾加上省略号。请在方法 __str__() 中添加一条 if 语句,以便仅在条目长度超过 50 字符时才添加省略号。使用管理网站来添加一个长度少于 50 字符的条目,并核实显示它时没有省略号。
18-4 比萨店 :新建一个名为 pizzeria 的项目,并在其中添加一个名为 pizzas 的应用程序。定义一个名为 Pizza 的模型,它包含字段 name ,用于存储比萨名称,如 Hawaiian 和 Meat Lovers 。定义一个名为 Topping 的模型,它包含字段 pizza和 name ,其中字段 pizza 是一个关联到 Pizza 的外键,而字段 name 用于存储配料,如 pineapple 、 Canadian bacon 和 sausage 。
向管理网站注册这两个模型,并使用管理网站输入一些比萨名和配料。使用 shell 来查看你输入的数据。
18-6 比萨店主页 :在你为完成练习 18-4 而创建的项目 Pizzeria 中,添加一个主页。
18-8 比萨店页面 :在练习 18-6 中开发的项目 Pizzeria 中添加一个页面,它显示供应的比萨的名称。然后,将每个比萨名称都设置成一个链接,单击这种链接将显示一个页面,其中列出了相应比萨的配料。请务必使用模板继承来高效地创建页面。
19-1 博客 :新建一个 Django 项目,将其命名为 Blog 。在这个项目中,创建一个名为 blogs 的应用程序,并在其中创建一个名为 BlogPost 的模型。这个模型应包含 title 、 text 和 date_added 等字段。为这个项目创建一个超级用户,并使用管理网站创建几个简短的帖子。创建一个主页,在其中按时间顺序显示所有的帖子。
创建两个表单,其中一个用于发布新帖子,另一个用于编辑既有的帖子。
尝试填写这些表单,确认它们能够正确地工作。
19-2 博客账户 :在你为完成练习 19-1 而开发的项目 Blog 中,添加一个用户身份验证和注册系统。让已登录的用户在屏幕上看到其用户
名,并让未注册的用户看到一个到注册页面的链接。
19-3 重构 :在 views.py 中,我们在两个地方核实主题关联到的用户为当前登录的用户。请将执行这种检查的代码放在一个名为 check_topic_owner() 的函数中,并在恰当的地方调用这个函数。
19-4 保护页面 new_entry :一个用户可在另一个用户的学习笔记中添加条目,方法是输入这样的 URL ,即其中包含输入另一个用户的主题的 ID 。为防范这种攻击,请在保存新条目前,核实它所属的主题归当前用户所有。
19-5 受保护的博客 :在你创建的项目 Blog 中,确保每篇博文都与特定用户相关联。确保任何用户都可访问所有的博文,但只有已登录的用户能够发表博文以及编辑既有博文。在让用户能够编辑其博文的视图中,在处理表单前确认用户编辑的是他自己发表的博文。
合并为项目“pizzeria ”,实现过程(Windows系统+python3.8.0+django3.0.1):
一.创建项目
- 新建项目目录“pizzeria”后,终端切换到这个目录下,执行命令“python -m venv ll_env”创建一个虚拟环境(虚拟环境是系统的一个位置,可以在其中安装工程包,并将其与其他 Python 包隔离,将项目的库与其他项目分离是有益的。);
- 执行命令“source ll_env/Scripts/activate”激活虚拟环境;
- 激活虚拟环境后,执行命令“pip install Django”来安装 Django:
- 用Django自带的脚手架工具“django-admin.py startproject pizzeria .”创建项目(末尾的句点让新项目使用合适的目录结构,这样开发完成后可轻松地将应用程序部署到服务器);
- 执行命令“python manage.py migrate”创建数据库:
二.创建应用程序
- 执行命令“python manage.py startapp pizzas”创建应用程序;
- 定义模型“Pizza ”和“Topping”(pizzas--models.py):
from django.db import models # Create your models here. class Pizza(models.Model): """ 披萨 """ # 由字符或文本组成的数据(限制长度100) name = models.CharField(max_length=100) # 记录日期和时间的数据(自动创建当前时间) date_added = models.DateTimeField(auto_now_add=True) def __str__(self): return self.name class Topping(models.Model): """ 配料 """ # 外键实例 # django 升级到2.0之后,表与表之间关联的时候,必须要写on_delete参数 # 由于多对多(ManyToManyField)没有 on_delete 参数,所以以上只针对外键(ForeignKey)和一对一(OneToOneField) pizza = models.ForeignKey(Pizza, on_delete=models.CASCADE) name = models.TextField() date_added = models.DateTimeField(auto_now_add=True) def __str__(self): """ 返回模型的字符串表示 """ # 长于50字符才截取和显示省略号 if len(self.name) > 50: return self.name[:50] + "..." else: return self.name
- 要使用模型,必须让 Django 将应用程序包含到项目中(pizzeria--settings.py):
- 执行命令“python manage.py makemigrations”创建迁移文件,然后执行命令“python manage.py migrate”进行数据库迁移(每次数据的修改,都需要执行以下三个步骤:修改models.py---创建迁移文件---进行数据库迁移):
- Git Bash执行命令“python manage.py createsuperuser”(创建超级用户)会报错“Superuser creation skipped due to not running in a TTY. You can run `manage.py c reatesuperuser` in your project to create one manually.”;需改成cmd执行,执行之前先激活虚拟环境“ll_env\Scripts\activate”,然后再执行“python manage.py createsuperuser”创建用户:
- 进行模型注册(pizzas--admin.py):
from django.contrib import admin from pizzas.models import Pizza, Topping # Register your models here. admin.site.register(Pizza) admin.site.register(Topping)
- 访问“http://127.0.0.1:8001/admin/login/”,并输入你创建的用户名和密码进行登录,首页显示如下:
三.创建网页(首页和其他页面)
- pizzas路由表接入到全局路由表(pizzeria--urls.py)并实现应用pizzas的路由表(文件夹 pizzas 中创建另一个 urls.py 文件):
# 导入项目和网站管理 URL 的函数和模块 from django.conf.urls import include, url from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), # namespace 将 pizzas 的 URL 同项目中的其他 URL 区分开来 # 源码函数def include(arg, namespace=None)中“urlconf_module, app_name = arg”,所以arg需要传入一个两元元组 url(r'', include(('pizzas.urls', 'pizzas'), namespace='pizzas')), ]
""" 定义 pizzas 的 URL 模式 """ from django.conf.urls import url from . import views urlpatterns = [ # 主页 # r让Python将接下来的字符串视为原始字符串,而引号告诉 Python 正则表达式始于和终于何处。脱字符( ^ )让 Python 查看字符串的开头, # 而美元符号让 Python 查看字符串的末尾。总体而言,这个正则表达式让 Python 查找开头和末尾之间没有任何东西的 URL 。 url(r'^$', views.index, name='index'), # 显示所有的配料 url(r'^pizzas/$', views.pizzas, name='pizzas'), # 特定主题的详细页面 # r 让 Django 将这个字符串视为原始字符串,并指出正则表达式包含在引号内。 # 这个表达式的第二部分( /(?P<pizza_id>\d+)/ )与包含在两个斜杠内的整数匹配,并将这个整数存储在一个名为 pizza_id 的实参中。 # 这部分表达式两边的括号捕获 URL 中的值; ?P<pizza_id> 将匹配的值存储到 pizza_id 中; # 而表达式 \d+ 与包含在两个斜杆内的任何数字都匹配,不管这个数字为多少位。 url(r'^pizza/(?P<pizza_id>\d+)/$', views.pizza, name='pizza'), ]
- 编写视图(pizzas--views.py):
from django.shortcuts import render from .models import Pizza # Create your views here. def index(request): """ 披萨的主页 """ return render(request, 'pizzas/index.html') def pizzas(request): """ 显示所有的配料 """ pizzas = Pizza.objects.order_by('date_added') context = {'pizzas': pizzas} return render(request, 'pizzas/pizzas.html', context) def pizza(request, pizza_id): """ 显示单个披萨及其所有的配料 """ pizza = Pizza.objects.get(id=pizza_id) # date_added 前面的减号指定按降序排列 toppings = pizza.topping_set.order_by('-date_added') context = {'pizza': pizza, 'toppings': toppings} return render(request, 'pizzas/pizza.html', context)
- 在文件夹 pizzas 中新建文件夹 templates--> 新建文件夹pizzas,为了使用模板继承来高效地创建页面,在末级pizzas新建文件base.html,编写代码如下:
<p> <a href="{% url 'pizzas:index' %}">pizzeria</a> - <a href="{% url 'pizzas:pizzas' %}">pizzas</a> </p> {% block content %}{% endblock content %}
- 在末级pizzas新建文件index.html(图1)、pizzas.html(图2)和pizza.html(图3),编写代码如下:
{% extends "pizzas/base.html" %} {% block content %} <p>Pizza, also known as Pizza, Pizza, Pizza, Pizza, is a food originated in Italy, is popular around the world.Pizza is usually made from fermented pita covered with tomato sauce, cheese and other ingredients and baked in an oven.</p> {% endblock content %}
{% extends "pizzas/base.html" %} {% block content %} <p>Pizzas</p> <ul> {% for pizza in pizzas %} <li> <a href="{% url 'pizzas:pizza' pizza.id %}">{{ pizza }}</a> </li> {% empty %} <li>No pizzas have been added yet.</li> {% endfor %} </ul> {% endblock content %}
{% extends 'pizzas/base.html' %} {% block content %} <p>Pizza: {{ pizza }}</p> <p>Toppings:</p> <ul> {% for topping in toppings %} <li> <!-- 竖线( | )表示模板过滤器 --> <p>{{ topping.date_added|date:'M d, Y H:i' }}</p> <p>{{ topping.name|linebreaks }}</p> </li> {% empty %} <li> There are no toppings for this pizza yet. </li> {% endfor %} </ul> {% endblock content %}
四.增加编辑披萨和配料功能
- 添加披萨和配料的表单(forms.py):
from django import forms from .models import Pizza, Topping class PizzaForm(forms.ModelForm): class Meta: model = Pizza fields = ['name'] # 让 Django 不要为字段 name 生成标签 labels = {'name': ''} class ToppingForm(forms.ModelForm): class Meta: model = Topping fields = ['name'] labels = {'name': ''} # 设置属性widgets,可覆盖Django选择的默认小部件 # 文本区域改成80列(非默认的40列) widgets = {'name': forms.Textarea(attrs={'cols': 80})}
- 应用pizzas的路由表增加新路由(后面三个):
""" 定义 pizzas 的 URL 模式 """ from django.conf.urls import url from . import views urlpatterns = [ # 主页 # r让Python将接下来的字符串视为原始字符串,而引号告诉 Python 正则表达式始于和终于何处。脱字符( ^ )让 Python 查看字符串的开头, # 而美元符号让 Python 查看字符串的末尾。总体而言,这个正则表达式让 Python 查找开头和末尾之间没有任何东西的 URL 。 url(r'^$', views.index, name='index'), # 显示所有的配料 url(r'^pizzas/$', views.pizzas, name='pizzas'), # 特定主题的详细页面 # r 让 Django 将这个字符串视为原始字符串,并指出正则表达式包含在引号内。 # 这个表达式的第二部分( /(?P<pizza_id>\d+)/ )与包含在两个斜杠内的整数匹配,并将这个整数存储在一个名为 pizza_id 的实参中。 # 这部分表达式两边的括号捕获 URL 中的值; ?P<pizza_id> 将匹配的值存储到 pizza_id 中; # 而表达式 \d+ 与包含在两个斜杆内的任何数字都匹配,不管这个数字为多少位。 url(r'^pizza/(?P<pizza_id>\d+)/$', views.pizza, name='pizza'), # 用于添加新披萨的页面 url(r'^new_pizza/$', views.new_pizza, name='new_pizza'), # 用于添加新配料的页面 url(r'^new_topping/(?P<pizza_id>\d+)/$', views.new_topping, name='new_topping'), # 用于编辑配料的页面 url(r'^edit_topping/(?P<topping_id>\d+)/$', views.edit_topping, name='edit_topping'), ]
- 修改视图(pizzas--views.py):
from django.shortcuts import render from django.http import HttpResponseRedirect # django2.0后把原来的django.core.urlresolvers包更改为了django.urls包 from django.urls import reverse from .models import Pizza, Topping from .forms import PizzaForm, ToppingForm # Create your views here. def index(request): """ 披萨的主页 """ return render(request, 'pizzas/index.html') def pizzas(request): """ 显示所有的配料 """ pizzas = Pizza.objects.order_by('date_added') context = {'pizzas': pizzas} return render(request, 'pizzas/pizzas.html', context) def pizza(request, pizza_id): """ 显示单个披萨及其所有的配料 """ pizza = Pizza.objects.get(id=pizza_id) # date_added 前面的减号指定按降序排列 toppings = pizza.topping_set.order_by('-date_added') context = {'pizza': pizza, 'toppings': toppings} return render(request, 'pizzas/pizza.html', context) def new_pizza(request): """ 添加新披萨 """ if request.method != 'POST': # 未提交数据:创建一个新表单 form = PizzaForm() else: # POST 提交的数据 , 对数据进行处理 form = PizzaForm(request.POST) # is_valid校验填写了必填信息和符合数据类型及长度 if form.is_valid(): form.save() # 将用户重定向到网页 pizzas return HttpResponseRedirect(reverse('pizzas:pizzas')) context = {'form': form} return render(request, 'pizzas/new_pizza.html', context) def new_topping(request, pizza_id): """ 在特定的披萨中添加新配料 """ pizza = Pizza.objects.get(id=pizza_id) if request.method != 'POST': # 未提交数据 , 创建一个空表单 form = ToppingForm() else: # POST 提交的数据 , 对数据进行处理 form = ToppingForm(data=request.POST) if form.is_valid(): # 传递了实参commit=False,让Django创建一个新的配料对象,并将其存储到new_topping中,但不将它保存到数据库中 new_topping = form.save(commit=False) new_topping.pizza = pizza # 把配料保存到数据库,并将其与正确的披萨相关联 new_topping.save() return HttpResponseRedirect(reverse('pizzas:pizza', args=[pizza_id])) context = {'pizza': pizza, 'form': form} return render(request, 'pizzas/new_topping.html', context) def edit_topping(request, topping_id): """ 编辑既有配料 """ topping = Topping.objects.get(id=topping_id) pizza = topping.pizza if request.method != 'POST': # 初次请求,使用当前条目填充表单(instance=topping) form = ToppingForm(instance=topping) else: # POST 提交的数据,对数据进行处理 form = ToppingForm(instance=topping, data=request.POST) if form.is_valid(): form.save() return HttpResponseRedirect(reverse('pizzas:pizza', args=[pizza.id])) context = {'topping': topping, 'pizza': pizza, 'form': form} return render(request, 'pizzas/edit_topping.html', context)
- 新建模板new_pizza.html(图1)、new_topping.html(图2)、edit_topping.html(图3):
{% extends "pizzas/base.html" %} {% block content %} <p>Add a new pizza:</p> <form action="{% url 'pizzas:new_pizza' %}" method='post'> <!-- 防止攻击者利用表单来获得对服务器未经授权的访问 --> {% csrf_token %} <!-- 显示表单(段落格式渲染) --> {{ form.as_p }} <button name="submit">add pizza</button> </form> {% endblock content %}
{% extends "pizzas/base.html" %} {% block content %} <p><a href="{% url 'pizzas:pizza' pizza.id %}">{{ pizza }}</a></p> <p>Add a new topping:</p> <form action="{% url 'pizzas:new_topping' pizza.id %}" method='post'> {% csrf_token %} {{ form.as_p }} <button name='submit'>add topping</button> </form> {% endblock content %}
{% extends "pizzas/base.html" %} {% block content %} <p><a href="{% url 'pizzas:pizza' pizza.id %}">{{ pizza }}</a></p> <p>Edit topping:</p> <form action="{% url 'pizzas:edit_topping' topping.id %}" method='post'> {% csrf_token %} {{ form.as_p }} <button name="submit">save changes</button> </form> {% endblock content %}
- 修改模板pizzas.html(图1)、pizza.html(图2):
{% extends "pizzas/base.html" %} {% block content %} <p>Pizzas</p> <ul> {% for pizza in pizzas %} <li> <a href="{% url 'pizzas:pizza' pizza.id %}">{{ pizza }}</a> </li> {% empty %} <li>No pizzas have been added yet.</li> {% endfor %} </ul> <a href="{% url 'pizzas:new_pizza' %}">Add a new pizza:</a> {% endblock content %}
{% extends 'pizzas/base.html' %} {% block content %} <p>Pizza: {{ pizza }}</p> <p>Toppings:</p> <p> <a href="{% url 'pizzas:new_topping' pizza.id %}">add new topping</a> </p> <ul> {% for topping in toppings %} <li> <!-- 竖线( | )表示模板过滤器 --> <p>{{ topping.date_added|date:'M d, Y H:i' }}</p> <p>{{ topping.name|linebreaks }}</p> <p> <a href="{% url 'pizzas:edit_topping' topping.id %}">edit topping</a> </p> </li> {% empty %} <li> There are no toppings for this pizza yet. </li> {% endfor %} </ul> {% endblock content %}
五.创建用户账户(登录、注销和注册)
- 执行命令“python manage.py startapp users”创建“users”的应用程序;
- 将应用程序 users 添加到 settings.py 中:
- users路由表接入到全局路由表(pizzeria--urls.py)并实现应用users的路由表(文件夹 users 中创建另一个 urls.py 文件):
# 导入项目和网站管理 URL 的函数和模块 from django.conf.urls import include, url from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^users/', include(('users.urls', 'users'), namespace='users')), # namespace 将 pizzas 的 URL 同项目中的其他 URL 区分开来 # 源码函数def include(arg, namespace=None)中“urlconf_module, app_name = arg”,所以arg需要传入一个两元元组 url(r'', include(('pizzas.urls', 'pizzas'), namespace='pizzas')), ]
""" 为应用程序 users 定义 URL 模式 """ from django.conf.urls import url from django.contrib.auth.views import LoginView from . import views urlpatterns = [ #登录界面 LoginView.as_view后面要加上() url(r'^login/$', LoginView.as_view(template_name='users/login.html'), name='login'), # 注销 url(r'^logout/$', views.logout_view, name='logout'), # 注册页面 url(r'^register/$', views.register, name='register'), ]
- 在文件夹 users 中新建文件夹 templates--> 新建文件夹users,在末级users新建文件login.html(图1)和register.html(图2),编写代码如下:
{% extends "pizzas/base.html" %} {% block content %} {% if form.errors %} <p>Your username and password didn't match. Please try again.</p> {% endif %} <form method="post" action="{% url 'users:login' %}"> {% csrf_token %} {{ form.as_p }} <button name="submit">log in</button> <input type="hidden" name="next" value="{% url 'pizzas:index' %}" /> </form> {% endblock content %}
{% extends "pizzas/base.html" %} {% block content %} <form method="post" action="{% url 'users:register' %}"> {% csrf_token %} {{ form.as_p }} <button name="submit">register</button> <input type="hidden" name="next" value="{% url 'pizzas:index' %}" /> </form> {% endblock content %}
- 修改base.html页面内容:
<p> <a href="{% url 'pizzas:index' %}">pizzeria</a> - <a href="{% url 'pizzas:pizzas' %}">pizzas</a> - {% if user.is_authenticated %} Hello, {{ user.username }}. <a href="{% url 'users:logout' %}">log out</a> {% else %} <a href="{% url 'users:register' %}">register</a> - <a href="{% url 'users:login' %}">log in</a> {% endif %} </p> {% block content %}{% endblock content %}
- 修改视图(users--views.py):
from django.shortcuts import render from django.http import HttpResponseRedirect from django.urls import reverse from django.contrib.auth import login, logout, authenticate from django.contrib.auth.forms import UserCreationForm def logout_view(request): """ 注销用户 """ logout(request) return HttpResponseRedirect(reverse('pizzas:index')) def register(request): """ 注册新用户 """ if request.method != 'POST': # 显示空的注册表单 form = UserCreationForm() else: # 处理填写好的表单 form = UserCreationForm(data=request.POST) if form.is_valid(): new_user = form.save() # 让用户自动登录,再重定向到主页 authenticated_user = authenticate(username=new_user.username, password=request.POST['password1']) login(request, authenticated_user) return HttpResponseRedirect(reverse('pizzas:index')) context = {'form': form} return render(request, 'users/register.html', context)
六.让用户拥有自己的数据
- 使用 @login_required 限制访问,除了首页、登录页面和注册页面,其它页面都只有已登录的用户才能访问数据;
- 只允许用户访问自己的数据,合并1的代码如下(pizzas-views.py):
from django.shortcuts import render from django.http import HttpResponseRedirect, Http404 # django2.0后把原来的django.core.urlresolvers包更改为了django.urls包 from django.urls import reverse from django.contrib.auth.decorators import login_required from .models import Pizza, Topping from .forms import PizzaForm, ToppingForm # Create your views here. def index(request): """ 披萨的主页 """ return render(request, 'pizzas/index.html') # login_required() 的代码检查用户是否已登录,仅当用户已登录时, Django 才运行 topics() 的代码。如果用户未登录,就重定向到登录页面。 @login_required def pizzas(request): """ 显示所有的配料 """ pizzas = Pizza.objects.filter(owner=request.user).order_by('date_added') context = {'pizzas': pizzas} return render(request, 'pizzas/pizzas.html', context) @login_required def pizza(request, pizza_id): """ 显示单个披萨及其所有的配料 """ pizza = Pizza.objects.get(id=pizza_id) # 请求的披萨不归当前用户所有,我们就引发 Http404 异常,让 Django 返回一个 404 错误页面 check_topic_owner(pizza, request) # date_added 前面的减号指定按降序排列 toppings = pizza.topping_set.order_by('-date_added') context = {'pizza': pizza, 'toppings': toppings} return render(request, 'pizzas/pizza.html', context) @login_required def new_pizza(request): """ 添加新披萨 """ if request.method != 'POST': # 未提交数据:创建一个新表单 form = PizzaForm() else: # POST 提交的数据 , 对数据进行处理 form = PizzaForm(request.POST) # is_valid校验填写了必填信息和符合数据类型及长度 if form.is_valid(): new_pizza = form.save(commit=False) new_pizza.owner = request.user new_pizza.save() # 将用户重定向到网页 pizzas return HttpResponseRedirect(reverse('pizzas:pizzas')) context = {'form': form} return render(request, 'pizzas/new_pizza.html', context) @login_required def new_topping(request, pizza_id): """ 在特定的披萨中添加新配料 """ pizza = Pizza.objects.get(id=pizza_id) # 解决一个用户可在另一个用户的学习笔记中添加条目问题 check_topic_owner(pizza, request) if request.method != 'POST': # 未提交数据 , 创建一个空表单 form = ToppingForm() else: # POST 提交的数据 , 对数据进行处理 form = ToppingForm(data=request.POST) if form.is_valid(): # 传递了实参commit=False,让Django创建一个新的配料对象,并将其存储到new_topping中,但不将它保存到数据库中 new_topping = form.save(commit=False) new_topping.pizza = pizza # 把配料保存到数据库,并将其与正确的披萨相关联 new_topping.save() return HttpResponseRedirect(reverse('pizzas:pizza', args=[pizza_id])) context = {'pizza': pizza, 'form': form} return render(request, 'pizzas/new_topping.html', context) @login_required def edit_topping(request, topping_id): """ 编辑既有配料 """ topping = Topping.objects.get(id=topping_id) pizza = topping.pizza check_topic_owner(pizza, request) if request.method != 'POST': # 初次请求,使用当前条目填充表单(instance=topping) form = ToppingForm(instance=topping) else: # POST 提交的数据,对数据进行处理 form = ToppingForm(instance=topping, data=request.POST) if form.is_valid(): form.save() return HttpResponseRedirect(reverse('pizzas:pizza', args=[pizza.id])) context = {'topping': topping, 'pizza': pizza, 'form': form} return render(request, 'pizzas/edit_topping.html', context) def check_topic_owner(pizza, request): """校验关联到的用户是否为当前登录的用户""" if pizza.owner != request.user: raise Http404
- settings.py末尾添加“LOGIN_URL = '/users/login/'”,使未登录时访问到校验需要登录的页面,重定向到登录页面;
- 将数据关联到用户,先修改models.py(图1,pizza增加用户外键),然后查找所有用户ID---使其数据(pizza)关联到超级用户---迁移数据库(图2);
from django.db import models from django.contrib.auth.models import User # Create your models here. class Pizza(models.Model): """ 披萨 """ # 由字符或文本组成的数据(限制长度100) name = models.CharField(max_length=100) # 记录日期和时间的数据(自动创建当前时间) date_added = models.DateTimeField(auto_now_add=True) owner = models.ForeignKey(User, on_delete=models.CASCADE) def __str__(self): return self.name class Topping(models.Model): """ 配料 """ # 外键实例 # django 升级到2.0之后,表与表之间关联的时候,必须要写on_delete参数 # 由于多对多(ManyToManyField)没有 on_delete 参数,所以以上只针对外键(ForeignKey)和一对一(OneToOneField) pizza = models.ForeignKey(Pizza, on_delete=models.CASCADE) name = models.TextField() date_added = models.DateTimeField(auto_now_add=True) def __str__(self): """ 返回模型的字符串表示 """ # 长于50字符才截取和显示省略号 if len(self.name) > 50: return self.name[:50] + "..." else: return self.name
- 切换不同用户登录只能查看自己的数据,访问非自己数据的URL提示404错误:
总结,整个项目结构如下: