Django之model外键
Posted Codsing-代码演奏者
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Django之model外键相关的知识,希望对你有一定的参考价值。
外键和表关系
外键:
在mysql
中,表有两种引擎,一种是InnoDB
,另外一种是myisam
。如果使用的是InnoDB
引擎,是支持外键约束的。外键的存在使得ORM
框架在处理表关系的时候异常的强大。因此这里我们首先来介绍下外键在Django
中的使用。
类定义为class ForeignKey(to,on_delete,**options)
。第一个参数是引用的是哪个模型,第二个参数是在使用外键引用的模型数据被删除了,这个字段该如何处理,比如有CASCADE
、SET_NULL
等。这里以一个实际案例来说明。比如有一个User
和一个Article
两个模型。一个User
可以发表多篇文章,一个Article
只能有一个Author
,并且通过外键进行引用。那么相关的示例代码如下:
class User(models.Model): username = models.CharField(max_length=20) password = models.CharField(max_length=100) class Article(models.Model): title = models.CharField(max_length=100) content = models.TextField() author = models.ForeignKey("User",on_delete=models.CASCADE)
以上使用ForeignKey
来定义模型之间的关系。即在article
的实例中可以通过author
属性来操作对应的User
模型。这样使用起来非常的方便。示例代码如下:
article = Article(title=\'abc\',content=\'123\') author = User(username=\'张三\',password=\'111111\') article.author = author article.save() # 修改article.author上的值 article.author.username = \'李四\' article.save()
为什么使用了ForeignKey
后,就能通过author
访问到对应的user
对象呢。因此在底层,Django
为Article
表添加了一个属性名_id
的字段(比如author的字段名称是author_id),这个字段是一个外键,记录着对应的作者的主键。以后通过article.author
访问的时候,实际上是先通过author_id
找到对应的数据,然后再提取User
表中的这条数据,形成一个模型。
如果想要引用另外一个app
的模型,那么应该在传递to
参数的时候,使用app.model_name
进行指定。以上例为例,如果User
和Article
不是在同一个app
中,那么在引用的时候的示例代码如下
# User模型在user这个app中 class User(models.Model): username = models.CharField(max_length=20) password = models.CharField(max_length=100) # Article模型在article这个app中 class Article(models.Model): title = models.CharField(max_length=100) content = models.TextField() author = models.ForeignKey("user.User",on_delete=models.CASCADE)
如果模型的外键引用的是本身自己这个模型,那么to
参数可以为\'self\'
,或者是这个模型的名字。在论坛开发中,一般评论都可以进行二级评论,即可以针对另外一个评论进行评论,那么在定义模型的时候就需要使用外键来引用自身。示例代码如下:
class Comment(models.Model): content = models.TextField() origin_comment = models.ForeignKey(\'self\',on_delete=models.CASCADE,null=True) # 或者 # origin_comment = models.ForeignKey(\'Comment\',on_delete=models.CASCADE,null=True)
外键删除操作:
如果一个模型使用了外键。那么在对方那个模型被删掉后,该进行什么样的操作。可以通过on_delete
来指定。可以指定的类型如下:
CASCADE
:级联操作。如果外键对应的那条数据被删除了,那么这条数据也会被删除。PROTECT
:受保护。即只要这条数据引用了外键的那条数据,那么就不能删除外键的那条数据。SET_NULL
:设置为空。如果外键的那条数据被删除了,那么在本条数据上就将这个字段设置为空。如果设置这个选项,前提是要指定这个字段可以为空。SET_DEFAULT
:设置默认值。如果外键的那条数据被删除了,那么本条数据上就将这个字段设置为默认值。如果设置这个选项,前提是要指定这个字段一个默认值。SET()
:如果外键的那条数据被删除了。那么将会获取SET
函数中的值来作为这个外键的值。SET
函数可以接收一个可以调用的对象(比如函数或者方法),如果是可以调用的对象,那么会将这个对象调用后的结果作为值返回回去。DO_NOTHING
:不采取任何行为。一切全看数据库级别的约束。
以上这些选项只是Django级别的,数据级别依旧是RESTRICT!
表关系:
表之间的关系都是通过外键来进行关联的。而表之间的关系,无非就是三种关系:一对一、一对多(多对一)、多对多等。以下将讨论一下三种关系的应用场景及其实现方式。
一对多:
- 应用场景:比如文章和作者之间的关系。一个文章只能由一个作者编写,但是一个作者可以写多篇文章。文章和作者之间的关系就是典型的多对一的关系。
-
实现方式:一对多或者多对一,都是通过
ForeignKey
来实现的。还是以文章和作者的案例进行讲解。
class User(models.Model): username = models.CharField(max_length=20) password = models.CharField(max_length=100) class Article(models.Model): title = models.CharField(max_length=100) content = models.TextField() author = models.ForeignKey("User",on_delete=models.CASCADE)
那么以后在给Article
对象指定author
,就可以使用以下代码来完成:
article = Article(title=\'abc\',content=\'123\') author = User(username=\'zhiliao\',password=\'111111\') # 要先保存到数据库中 author.save() article.author = author article.save()
并且以后如果想要获取某个用户下所有的文章,可以通过article_set
来实现。示例代码如下:
user = User.objects.first() # 获取第一个用户写的所有文章 articles = user.article_set.all() for article in articles: print(article)
一对一:
-
应用场景:比如一个用户表和一个用户信息表。在实际网站中,可能需要保存用户的许多信息,但是有些信息是不经常用的。如果把所有信息都存放到一张表中可能会影响查询效率,因此可以把用户的一些不常用的信息存放到另外一张表中我们叫做
UserExtension
。但是用户表User
和用户信息表UserExtension
就是典型的一对一了。 -
实现方式:
Django
为一对一提供了一个专门的Field
叫做OneToOneField
来实现一对一操作。示例代码如下:
class User(models.Model): username = models.CharField(max_length=20) password = models.CharField(max_length=100) class UserExtension(models.Model): birthday = models.DateTimeField(null=True) school = models.CharField(blank=True,max_length=50) user = models.OneToOneField("User", on_delete=models.CASCADE)
在UserExtension
模型上增加了一个一对一的关系映射。其实底层是在UserExtension
这个表上增加了一个user_id
,来和user
表进行关联,并且这个外键数据在表中必须是唯一的,来保证一对一。
多对多:
-
应用场景:比如文章和标签的关系。一篇文章可以有多个标签,一个标签可以被多个文章所引用。因此标签和文章的关系是典型的多对多的关系。
-
实现方式:
Django
为这种多对多的实现提供了专门的Field
。叫做ManyToManyField
。还是拿文章和标签为例进行讲解。示例代码如下:
class Article(models.Model): title = models.CharField(max_length=100) content = models.TextField() tags = models.ManyToManyField("Tag",related_name="articles") class Tag(models.Model): name = models.CharField(max_length=50)
在数据库层面,实际上Django
是为这种多对多的关系建立了一个中间表。这个中间表分别定义了两个外键,引用到article
和tag
两张表的主键。
related_name和related_query_name:
related_name:
还是以User
和Article
为例来进行说明。如果一个article
想要访问对应的作者,那么可以通过author
来进行访问。但是如果有一个user
对象,想要通过这个user
对象获取所有的文章,该如何做呢?这时候可以通过user.article_set
来访问,这个名字的规律是模型名字小写_set
。示例代码如下:
user = User.objects.get(name=\'张三\') user.article_set.all()
如果不想使用模型名字小写_set
的方式,想要使用其他的名字,那么可以在定义模型的时候指定related_name
。示例代码如下:
class Article(models.Model): title = models.CharField(max_length=100) content = models.TextField() # 传递related_name参数,以后在方向引用的时候使用articles进行访问 author = models.ForeignKey("User",on_delete=models.SET_NULL,null=True,related_name=\'articles\')
以后在方向引用的时候。使用articles
可以访问到这个作者的文章模型。示例代码如下:
user = User.objects.get(name=\'张三\') user.articles.all()
如果不想使用反向引用,那么可以指定related_name=\'+\'
。示例代码如下:
class Article(models.Model): title = models.CharField(max_length=100) content = models.TextField() # 传递related_name参数,以后在方向引用的时候使用articles进行访问 author = models.ForeignKey("User",on_delete=models.SET_NULL,null=True,related_name=\'+\')
后将不能通过user.article_set
来访问文章模型了。
related_query_name:
在查找数据的时候,可以使用filter
进行过滤。使用filter
过滤的时候,不仅仅可以指定本模型上的某个属性要满足什么条件,还可以指定相关联的模型满足什么属性。比如现在想要获取写过标题为abc
的所有用户,那么可以这样写:
users = User.objects.filter(article__title=\'abc\')
如果你设置了related_name
为articles
,因为反转的过滤器的名字将使用related_name
的名字,那么上例代码将改成如下:
users = User.objects.filter(articles__title=\'abc\')
可以通过related_query_name
将查询的反转名字修改成其他的名字。比如article
。示例代码如下:
class Article(models.Model): title = models.CharField(max_length=100) content = models.TextField() # 传递related_name参数,以后在方向引用的时候使用articles进行访问 author = models.ForeignKey("User",on_delete=models.SET_NULL,null=True,related_name=\'articles\',related_query_name=\'article\')
那么在做反向过滤查找的时候就可以使用以下代码:
users = User.objects.filter(article__title=\'abc\')
指定外键字段:
class Game(models.Model): GAME_AREAS = ( ) GAME_VALIDS = ( (0, \'无效\'), (1, \'有效\'), ) appid = models.IntegerField(primary_key=True, unique=True, verbose_name="游戏标识") name = models.CharField(max_length=32, verbose_name="游戏名称") area = models.CharField(max_length=32, choices=GAME_AREAS, verbose_name="区域") is_valid = models.SmallIntegerField(choices=GAME_VALIDS, verbose_name="是否有效") def __str__(self): return self.name class Meta: db_table = "common_game" verbose_name = verbose_name_plural = "游戏表" class GameService(models.Model): appid = models.ForeignKey(Game, db_column=\'appid\', null=True, on_delete=models.SET_NULL, verbose_name=\'游戏\') # 指定db_column字段
为 Django 模型自动填充外键
【中文标题】为 Django 模型自动填充外键【英文标题】:Auto fill ForeignKey for Django Model 【发布时间】:2012-10-21 18:10:00 【问题描述】:所以我正在构建一个基本的问答网站——每个主题都有一系列与之相关的问题,每个问题都有与之相关的多个答案。
我正在为问题创建用户输入,它们必须与某个主题相关联。这是问题模型
#models.py
class Question(models.Model):
movie = models.ForeignKey(Movie, blank=True, null=True)
question_text = models.CharField(max_length = 1000)
question_detail = models.CharField(max_length = 5000, blank = True, null = True)
q_pub_date = models.DateTimeField(auto_now_add = True)
q_author = models.ForeignKey(User)
class QuestionForm(ModelForm):
def save(self, user = None, force_insert = False, force_update = False, commit = True):
q = super(QuestionForm, self).save(commit = False)
q.q_author = user
if commit:
q.save()
return q
class Meta:
model = Question
exclude = ('movie', 'q_author', 'q_pub_date')
这是网址配置
#urls.py
url(r'^(?P<movie_id>\d+)/add_question/$', 'add_question'),
现在是视图
#views.py
def add_question(request, movie_id):
if request.method == "POST":
form = QuestionForm(request.POST, request.FILES)
#QuestionForm.movie = Movie.objects.get(pk = movie_id)
if form.is_valid():
form.save(user = request.user)
return HttpResponseRedirect("/home/")
else:
form = QuestionForm()
return render_to_response("qanda/add_question.html", 'form': form, context_instance = RequestContext(request))
这是 HTML 代码
#add_question.html
<h1> Add Question: user.username </h1>
<form action = "" method = "post">% csrf_token %
form.as_p
<input type = "submit" value = "Ask" />
<input type = "hidden" name = "next" value = " next|escape " />
</form>
在视图中,注释掉的行是我添加到视图中以尝试自动保存模型的内容。添加问题时,URL 具有与其关联的电影的 ID,我的想法是获取该 ID,然后将其插入 ForeignKey 以识别与问题关联的电影。但是,当我使用我的代码时,它会将所有问题的电影关联更改为当前电影,而不仅仅是更改特定问题的电影关联。没有代码,它根本不会将电影与问题相关联。我该如何解决这个问题?
【问题讨论】:
【参考方案1】:使用这个:
#views.py
def add_question(request, movie_id):
if request.method == "POST":
form = QuestionForm(request.POST, request.FILES)
if form.is_valid():
question = form.save(user = request.user)
question.movie = Movie.objects.get(pk = movie_id)
question.save()
return HttpResponseRedirect("/home/")
else:
form = QuestionForm()
return render_to_response("qanda/add_question.html", 'form': form, context_instance = RequestContext(request)
评论中提出的问题
您应该避免在视图或模板中使用绝对 URL。考虑一个场景,您决定将home
URL 从/home/
更改为/myhome/
。您必须在曾经使用过它们的地方对其进行编辑。命名网址总是更好的(docs):
# URL Conf
url(r'^home/$', 'home_view', name="home_url"),
url(r'^(?P<movie_id>\d+)/add_question/$', 'add_question', name="add_question_url"),
url(r'^home/(?P<movie_id>\d+)/$', 'movie_view', name="movie_url"),
name
参数充当您实际 URL 的唯一标识符
现在在你看来:
from django.core.urlresolvers import reverse
def some_view(request):
...
return HttpResponseRedirect(reverse('home_url'))
现在,您对 URL 所做的任何更改(比如 /home/ 到 /myhome/ 对视图都没有影响,只要 name
参数在 URL conf 中具有相同的值。
如果你想传递参数(比如你的电影 ID)
def some_view(request, movie_id):
...
return HttpResponseRedirect(reverse('movie_url', kwargs='movie_id':movie_id))
应在模板中使用相同的概念,以避免在模板中硬编码 URL。请阅读this了解更多详情
【讨论】:
太棒了。完美地工作。多谢!还有一个——如何将用户重定向回上一个 URL?我可以将变量放入 HttpResponseRedirect("home/(?P以上是关于Django之model外键的主要内容,如果未能解决你的问题,请参考以下文章