如何优化这个多对多的Django ORM查询和模型集?
我有一个Django查询和一些Python代码,我想优化一下,因为1)它看起来很丑,而且性能不如我可以用SQL写的那样好,2)因为数据的层次重组对我来说看起来很乱。
所以,
- 有没有可能把这个优化成一个查询?
- 我该如何改进我的Python代码,让它更符合Python的风格?
背景
这是一个照片库系统。这个特定的视图是想展示图库中所有照片的缩略图。每张照片的大小都是固定的,以避免动态调整大小,我还想获取每种大小的URL和“大小类型”(例如,缩略图、中等、大号),这样我就可以在不再次访问数据库的情况下显示其他尺寸的照片。
相关实体
我有5个相关的模型:
class Gallery(models.Model):
Photos = models.ManyToManyField('Photo', through = 'GalleryPhoto', blank = True, null = True)
class GalleryPhoto(models.Model):
Gallery = models.ForeignKey('Gallery')
Photo = models.ForeignKey('Photo')
Order = models.PositiveIntegerField(default = 1)
class Photo(models.Model):
GUID = models.CharField(max_length = 32)
class PhotoSize(models.Model):
Photo = models.ForeignKey('Photo')
PhotoSizing = models.ForeignKey('PhotoSizing')
PhotoURL = models.CharField(max_length = 1000)
class PhotoSizing(models.Model):
SizeName = models.CharField(max_length = 20)
Width = models.IntegerField(default = 0, null = True, blank = True)
Height = models.IntegerField(default = 0, null = True, blank = True)
Type = models.CharField(max_length = 10, null = True, blank = True)
大致的想法是,我想通过GalleryPhoto获取图库中的所有照片,并且对于每张照片,我想获取所有的PhotoSizes,我希望能够通过字典遍历并访问所有这些数据。
SQL的大致草图可能是这样的:
Select PhotoSize.PhotoURL
From PhotoSize
Inner Join Photo On Photo.id = PhotoSize.Photo_id
Inner Join GalleryPhoto On GalleryPhoto.Photo_id = Photo.id
Inner Join Gallery On Gallery.id = GalleryPhoto.Gallery_id
Where Gallery.id = 5
Order By GalleryPhoto.Order Asc
我想把它变成一个这样的列表:
(
photo: {
'guid': 'abcdefg',
'sizes': {
'Thumbnail': 'http://mysite/image1_thumb.jpg',
'Large': 'http://mysite/image1_full.jpg',
more sizes...
}
},
more photos...
)
我目前有以下Python代码(虽然它并不完全符合上面的结构,但可以作为一个例子)。
gallery_photos = [(photo.Photo_id, photo.Order) for photo in GalleryPhoto.objects.filter(Gallery = gallery)]
photo_list = list(PhotoSize.objects.select_related('Photo', 'PhotoSizing').filter(Photo__id__in=[gallery_photo[0] for gallery_photo in gallery_photos]))
photos = {}
for photo in photo_list:
order = 1
for gallery_photo in gallery_photos:
if gallery_photo[0] == photo.Photo.id:
order = gallery_photo[1] //this gets the order column value
guid = photo.Photo.GUID
if not guid in photos:
photos[guid] = { 'Photo': photo.Photo, 'Thumbnail': None, 'Sizes': [], 'Order': order }
photos[guid]['Sizes'].append(photo)
sorted_photos = sorted(photos.values(), key=operator.itemgetter('Order'))
实际问题,第一部分
所以,我的问题首先是,是否可以更好地处理我的多对多查询,这样我就不需要对gallery_photos和photo_list进行两次查询。
实际问题,第二部分
我看着这段代码,觉得它的样子不太好。我希望有更好的方法通过列名将层次查询结果分组到字典中。有没有这样的办法?
3 个回答
你可以通过一次查询获取所有数据,并得到一个数据字典的列表。然后你可以管理这个字典,或者创建一个新的字典来形成你最终想要的字典……你可以在过滤和选择表中特定行时使用反向关系……所以:
假设x
是你选择的画廊……
GalleryPhoto.objexts.filter(Galery=x).values('Order', 'Photo__GUID', 'Photo__Photo__PhotoURL', 'Photo__Photo__PhotoSizing__SizeName', 'Photo__Photo__PhotoSizing__Width', 'Photo__Photo__PhotoSizing__Height', 'Photo__Photo__PhotoSizing__Type')
使用Photo__
会创建一个与Photo
表的内连接,而Photo__Photo__
会通过反向关系创建一个与PhotoSize
的内连接,Photo__Photo__PhotoSizing__
则会与PhotoSizing
进行内连接……
这样你就得到了一个字典列表:
[{'Order':....,'GUID': ..., 'PhotoURL':....., 'SizeName':...., 'Width':...., 'Height':..., 'Type':...}, {'Order':....,'GUID': ..., 'PhotoURL':....., 'SizeName':...., 'Width':...., 'Height':..., 'Type':...},....]
你可以选择你需要的行,并将所有值作为字典列表获取……然后你可以写一个循环函数或者迭代器,遍历这个列表,创建一个新的字典来对你的数据进行分组……
Django有一些内置的功能,可以让你的代码看起来更整洁。这会导致一些子查询,所以性能可能会受到影响。你可以查看这个链接了解更多信息:https://docs.djangoproject.com/en/dev/ref/models/querysets/#django.db.models.query.QuerySet.values
gallery_photos = GalleryPhoto.objects.filter(Gallery=gallery).values('Photo_id', 'Order')
photo_queryset = PhotoSize.objects.selected_related('Photo', 'PhotoSizing').filter(
Photo__id__in=gallery_photos.values_list('Photo_id', flat=True))
调用list()
会立即计算出查询结果,如果你的数据量很大,这可能会影响性能。
另外,有一种比较简单的方法可以去掉if gallery_photo[0] == photo.Photo.id:
这段代码。看起来可以通过另一个查询来轻松解决,获取所有照片的画廊照片。
当你有一个SQL查询,使用ORM(对象关系映射)写起来很困难时,可以考虑使用PostgreSQL的视图。不太确定MySQL是否也能这样做。在这种情况下,你会看到:
原始的SQL代码,比如:
CREATE VIEW photo_urls AS
Select
photo.id, --pseudo primary key for django mapper
Gallery.id as gallery_id,
PhotoSize.PhotoURL as photo_url
From PhotoSize
Inner Join Photo On Photo.id = PhotoSize.Photo_id
Inner Join GalleryPhoto On GalleryPhoto.Photo_id = Photo.id
Inner Join Gallery On Gallery.id = GalleryPhoto.Gallery_id
Order By GalleryPhoto.Order Asc
Django模型的样子:
class PhotoUrls(models.Model):
class Meta:
managed = False
db_table = 'photo_urls'
gallery_id = models.IntegerField()
photo_url = models.CharField()
ORM查询集的样子:
PhotoUrls.objects.filter(gallery_id=5)
希望这些对你有帮助。