Python Django ManyToManyField在数据库中的位置/方式?
更新:刚刚发现,当选择特定相册时,ManyToManyField导致管理界面崩溃。我注释掉了它们,注释掉了对它的所有引用,重新运行了Python Django ManyToManyField在数据库中的位置/方式?,python,django,Python,Django,更新:刚刚发现,当选择特定相册时,ManyToManyField导致管理界面崩溃。我注释掉了它们,注释掉了对它的所有引用,重新运行了makemigrations和migrate,现在管理界面又开始工作了……这让我离让这个“最喜欢的”专栏工作更远了:(见下面的内容: 背景:我的目标是使此网页中的“收藏”列反映当前登录用户的收藏相册,其中每个相册都是“否”或“是”,并且是一个可单击的链接,用于切换选择。(未登录时,它们都将是灰色的“n/a”-s。) 因此,对于每个相册,每个用户可能有零个或一个“
makemigrations
和migrate
,现在管理界面又开始工作了……这让我离让这个“最喜欢的”专栏工作更远了:(见下面的内容:
背景:我的目标是使此网页中的“收藏”列反映当前登录用户的收藏相册,其中每个相册都是“否”或“是”,并且是一个可单击的链接,用于切换选择。(未登录时,它们都将是灰色的“n/a”-s。) 因此,对于每个相册,每个用户可能有零个或一个“has favorited”条目。如果条目存在,则表示他们喜欢它。如果条目不存在,则表示他们不喜欢它 这是我的
相册
模型,其中包含用户喜爱的多对多列(底部是完整的models.py
):
我最初有这个FavoriteAlbum
模型,但由于它除了外键之外没有额外的信息,建议我删除它以支持上面的多对多列
class FavoriteSongs(models.Model):
user = models.ForeignKey(User)
song = models.ForeignKey(Song)
class Meta:
unique_together = ('user', 'song',)
def __str__(self):
return "user=" + str(self.user) + ", song=" + str(self.song)
我需要做的是在相册和用户之间进行“左连接”,所有相册都被选中,并且当前登录用户的所有收藏夹都被连接到相册中(None
,如果他们不喜欢它)。我不知道该怎么办
我还听说了执行此联接的函数。视图的get\u queryset()
中当前正在工作的查询是
return super(AlbumList, self).get_queryset().order_by("pub_date")
(下面是完整的views.py
)我目前的猜测是:
return super(AlbumList,self)。get_queryset()。order_by(“pub_date”)。extra(select={“is_favorite”:“favorited_by_users_id=“+str(request.user.id))
但是,虽然这不会崩溃,但模板中每个{{is_favorite}}
的值都是nothing(空字符串)。这是有意义的,因为数据库中还没有任何内容,但是现在呢?我不知道这是否是正确的Django查询
我想在数据库中添加一个项目来测试这一点,在postgres中使用一个手动SQL语句(目前还没有通过Django命令),但是如何以及在何处这样做呢
我已经成功地运行了makemigrations
,然后使用这个新的m2m列运行了migrate
(并且没有favoriteshongs
模型),但我在数据库中看不到任何表示is favorite值的内容。在billyjoel_album
中没有额外的列,也没有类似于billyjoel_favoritealbum
的直通表。那么,这些数据在数据库中存储在哪里/如何存储
(关于这个额外的“最爱”专栏的任何其他建议也将不胜感激!)
谢谢
models.py
from django.db import models
from django.contrib.auth.models import User
from time import time
def get_upload_file_name(instance, filename):
return "uploaded_files/%s_%s" % (str(time()).replace(".", "_"), filename)
class Album(models.Model):
OFFICIALITY = (
('J', 'Major studio release'),
('I', 'Non-major official release'),
('U', 'Unofficial'),
)
title = models.CharField(max_length=70)
description = models.TextField(max_length=500, default="", null=True, blank=True)
pub_date = models.DateField('release date')
officiality = models.CharField(max_length=1, choices=OFFICIALITY)
is_concert = models.BooleanField(default=False)
main_info_url = models.URLField(blank=False)
thumbnail = models.FileField(upload_to=get_upload_file_name, blank=True, null=True)
#virtual field to skip over the through table.
songs = models.ManyToManyField("Song", through="AlbumSong")
favorited_by_users = models.ManyToManyField(User)
def __str__(self):
return self.title
class Meta:
#Default ordering is by release date, ascending.
ordering = ['pub_date']
class Song(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(max_length=500, default="", null=True, blank=True)
length_seconds = models.IntegerField()
lyrics_url = models.URLField(default="", blank=True, null=True)
albums = models.ManyToManyField("Album", through="AlbumSong")
favorited_by_users = models.ManyToManyField(User)
def get_length_desc_from_seconds(self):
if(self.length_seconds == -1):
return "-1"
m, s = divmod(self.length_seconds, 60)
h, m = divmod(m, 60)
if(h):
return "%d:%02d:%02d" % (h, m, s)
else:
return "%d:%02d" % (m, s)
def __str__(self):
return self.name
class AlbumSong(models.Model):
song = models.ForeignKey(Song)
album = models.ForeignKey(Album)
sequence_num = models.IntegerField()
class Meta:
unique_together = ('album', 'sequence_num',)
unique_together = ('album', 'song',)
def __str__(self):
return str(self.album) + ": " + str(self.sequence_num) + ": " + str(self.song)
views.py
from .models import Album, Song, AlbumSong
from datetime import datetime, timedelta
from django.core.context_processors import csrf
from django.shortcuts import render, render_to_response
from django.views.generic import DetailView, ListView
from enum import Enum
def get_str_with_appended(string, between_if_str_non_empty, new_value):
if(len(string) == 0):
return new_value
else:
return string + between_if_str_non_empty + new_value
class PrependQuestionMark(Enum):
YES, NO = range(2)
def get_url_param_string_from_params(prepend_question_mark=PrependQuestionMark.YES, **kwargs_all_params):
param_list = ""
for key in iter(kwargs_all_params):
value = kwargs_all_params[key]
if(value is not None):
param_list = get_str_with_appended(param_list, '&', str(key) + "=" + str(value))
if(len(param_list) == 0):
return param_list;
if(prepend_question_mark == PrependQuestionMark.YES):
return "?" + param_list
else:
return param_list
class AlbumList(ListView):
model = Album
context_object_name = "albums"
#Derived from irc/#dango/tbaxter...START
def dispatch(self, request, *args, **kwargs):
#default to asc
self.sort_order = request.GET.get("sort_order", None)
self.sort_item = request.GET.get("sort_item", None)
self.csrf_token = csrf(request)["csrf_token"]
self.logged_in_user = request.user
#self.csrf_token = request.GET.get("csrf_token", None)
return super(AlbumList, self).dispatch(request, *args, **kwargs)
def get_queryset(self):
#Item zero in both is the default
#should be static global
asc_desc_list = ["asc", "dsc"]
sort_by_types = ["pub_date", "title"]
if(self.sort_order is None and self.sort_item is None):
#Use default ordering
return super(AlbumList, self).get_queryset()
#Custom ordering requested
sort_order = self.sort_order
sort_item = self.sort_item
if(sort_order is None or
sort_order not in asc_desc_list):
sort_order = asc_desc_list[0]
if(sort_item is None or
sort_item not in sort_by_types):
sort_item = sort_by_types[0]
order_minus = "" if sort_order == "asc" else "-"
return super(AlbumList, self).get_queryset().order_by(order_minus + sort_item).extra(select={"is_favorite": "favorited_by_users__id = " + str(self.logged_in_user.id) })
def get_context_data(self, **kwargs):
context = super(AlbumList, self).get_context_data(**kwargs)
context["sort_order"] = self.sort_order
context["sort_item"] = self.sort_item
context["url_params"] = get_url_param_string_from_params(
sort_item=self.sort_item,
sort_order=self.sort_order,
csrf_token=self.csrf_token)
return context
class AlbumDetail(DetailView):
model = Album
context_object_name = "album"
def dispatch(self, request, *args, **kwargs):
#default to asc
self.sort_order = request.GET.get("sort_order", None)
self.sort_item = request.GET.get("sort_item", None)
self.csrf_token = csrf(request)["csrf_token"]
return super(AlbumDetail, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
#Call the base implementation first to get a context
context = super(AlbumDetail, self).get_context_data(**kwargs)
#Add in the required extra info: album_songs, ordered by
#sequence_num
#select_related is to query the database for all songs at once, here
#in the view, to prevent the template from pin-pricking the database
#in each for loop iteration. For large datasets, this is critical.
context['album_songs'] = kwargs["object"].albumsong_set.order_by('sequence_num').select_related("song")
context["url_params"] = get_url_param_string_from_params(
sort_item=self.sort_item,
sort_order=self.sort_order,
csrf_token=self.csrf_token)
return context
相册列表.html
{% extends "base.html" %}
{% load bj_filters %}
{% block title %}Billy Joel Album Browser{% endblock %}
{% block sidebar %}
<UL>
<LI><a href="{% url 'album_list' %}{{ url_params }}">All albums</A></LI>
<LI><a href="/admin/">Admin</A></LI>
</UL>
{% endblock %}
{% block content %}
<TABLE ALIGN="center" WIDTH="100%" BORDER="1" CELLSPACING="0" CELLPADDING="4" BGCOLOR="#EEEEEE"><TR ALIGN="center" VALIGN="middle">
{% if user.is_authenticated %}
<TD>My profile (<a href="{% url 'accounts_logout' %}">Logout</A>)</TD>
{% else %}
<TD><a href="{% url 'accounts_login' %}">Login</A> to view your favorites</TD>
{% endif %}
</TR></TABLE>
<H1>Billy Joel Album Browser</H1>
<!--
<P>url_params={{ url_params }}</P>
-->
{% if albums.count > 0 %}
<P>Officiality: <IMG SRC="/static/images/major.jpg" height="20"/>=Major studio release, <IMG SRC="/static/images/minor.jpg" height="20"/>=Official release, <IMG SRC="/static/images/unofficial.jpg" height="20"/>=Unofficial</P>
<TABLE ALIGN="center" WIDTH="100%" BORDER="1" CELLSPACING="0" CELLPADDING="4" BGCOLOR="#EEEEEE"><TR ALIGN="center" VALIGN="middle">
<TD><B><U><a href="{% url 'album_list' %}?sort_item=title&sort_order=
{% if sort_item == 'pub_date' %}asc{% else %}
{{ sort_order|multival_to_str:'asc,dsc->dsc,asc,dsc' }}
{% endif %}
&csrf_token={{ csrf_token }}">Title</A></U></B><BR><I><FONT SIZE="-1">(click a title to view its song list)</FONT></I></TD>
<TD><B><U><a href="{% url 'album_list' %}?sort_item=pub_date&sort_order=
{% if sort_item == 'title' %}asc{% else %}
{{ sort_order|multival_to_str:'asc,dsc->dsc,asc,dsc' }}
{% endif %}
&csrf_token={{ csrf_token }}">Released</A></U></B></TD>
<TD>Officiality</TD>
<TD>Concert</TD>
<TD>Wiki</TD>
<TD>Favorite?</TD>
{% for album in albums %} <!-- No colon after "albums" -->
</TR><TR>
<TD VALIGN="top">
{% if album.thumbnail %}
<img src="/static/{{ album.thumbnail }}" width="25"/>
{% else %}
<img src="/static/images/white_block.jpg" width="25"/>
{% endif %}
<a href="/albums/get/{{ album.id }}{{ url_params }}">{{ album.title }}</a>
{% if album.description %}
<BR/><FONT SIZE="-1"><I>{{ album.description|truncatewords:10 }}</I></FONT>
{% endif %}
<TD>{{ album.pub_date|date:"m/y" }}</TD>
<TD><IMG SRC="/static/images/{{ album.officiality|multival_to_str:"J,I,U->major,minor,unofficial,broken_image"}}.jpg" height="20"/></TD>
<TD>{{ album.is_concert|yesno:"Yes,No" }}</TD>
<TD><A HREF="{{ album.main_info_url }}">Wiki</A></TD>
<TD><I>n/a {{ is_favorite }}</I></TD>
{% endfor %}
</TR></TABLE>
{% else %}
<P><I>There are no albums in the database.</I></P>
{% endif %}
{% endblock %}
{%extends“base.html”%}
{load bj_过滤器%load}
{%block title%}比利·乔尔相册浏览器{%endblock%}
{%块边栏%}
{%endblock%}
{%block content%}
{%if user.u经过身份验证%}
我的个人资料()
{%else%}
查看您的收藏夹
{%endif%}
比利·乔尔相册浏览器
{如果albums.count>0%,则为%0}
官方性:=主要工作室发行版,=官方发行版,=非官方发行版
(单击标题可查看其歌曲列表)
官方
音乐会
维基
最喜欢的
{相册%中相册的%s}
{%if album.thumboil%}
{%else%}
{%endif%}
{%if album.description%}
{{album.description}truncatewords:10}
{%endif%}
{{album.pub_date}日期:“m/y”}
大调、小调、非正式、不完整的_image“}}.jpg“height=”20“/>
{{album.is|u concert}是否:“是,否”}
不适用{{是你最喜欢的}
{%endfor%}
{%else%}
数据库中没有相册。
{%endif%}
{%endblock%}
多对多字段在数据库中的表示方式与您最初的FavoriteShongs模型完全相同,它是一个链接表,包含歌曲和用户的外键。摆脱FavoriteShongs的唯一好处是,您现在使用的是一个自动定义的直通表,而不是手动表
我不理解您的示例查询,因为您没有说明实际调用它的是什么模型,或者self.logged\u user
是什么。但是,您不能像这样使用extra
:您试图将Django查询语法放在那里,用双下划线名称来遍历关系,但是extra
被传递直接连接到SQL,而这对语法一无所知
我不会在一次查询中尝试这样做。相反,我会进行两次查询,一次获取所有相册,另一次获取用户的收藏夹。get\u queryset
只返回完整的相册列表,然后您可以使用get\u context\u data
获取表示收藏夹ID的附加对象集:
favorites = self.logged_in_user.album_set.all().values_list('id', flat=True)
context['favorites'] = set(favorites)
values_列表只获取相册的ID,因为这就是我们所需要的,然后我们将它们放入一个集合中,以加快查找速度
现在,在模板中,您只需执行以下操作:
{% for album in albums %}
...
<td>{% if album.id in favorites %}Yes{% else %}No{% endif %}</td>
{% endfor %}
{相册%中相册的%
...
{%if album.id在收藏夹%}是{%else%}否{%endif%}
{%endfor%}
M2M关系是全新创建的表。它们具有唯一的名称,并具有两个外键。创建了复合键,因此直接和相关模型组合是唯一的
当您这样做时:
class Topping(models.Model):
name = ...
class Pizza(models.Model):
name = ...
toppings = models.ManyToManyField(Topping, related_name="pizzas")
#not including a related_name will generate a "pizza_set" related name.
此时会出现一个新表,用内部名称描述此关系。此表具有pizza_id和Toping_id外键,以及包含这两个字段的复合唯一键。您不能也不应该预测此类表的名称
另一方面,如果要访问关系,并在可能的情况下声明更多字段,则可以:
class Topping(models.Model):
name = ...
class Pizza(models.Model):
name = ...
toppings = models.ManyToManyField(Topping, related_name="pizzas", through="PizzaAndTopping")
#not including a related_name will generate a "pizza_set" related name.
class PizzaAndTopping(models.Model):
more_data = models.TextField()
pizza = models.ForeignKey(Pizza, null=False)
topping = models.ForeignKey(Topping, null=False)
class Meta:
unique_together = (('pizza','topping'),)
注意如何
class Topping(models.Model):
name = ...
class Pizza(models.Model):
name = ...
toppings = models.ManyToManyField(Topping, related_name="pizzas", through="PizzaAndTopping")
#not including a related_name will generate a "pizza_set" related name.
class PizzaAndTopping(models.Model):
more_data = models.TextField()
pizza = models.ForeignKey(Pizza, null=False)
topping = models.ForeignKey(Topping, null=False)
class Meta:
unique_together = (('pizza','topping'),)
Pizza.objects.get(pk=1).toppings.append(Topping.objects.get(pk=2))
[1, 2, 4, 5, 10, ...] #you'll get a list of ids