uranusjr / django-tutorial-for-programmers

Chapter text and example code for the Django tutorial first appeared during ITHome Ironman 7
289 stars 93 forks source link

介紹一些 Model 與 Manager 的使用策略 #24

Open uranusjr opened 9 years ago

uranusjr commented 9 years ago

Fat model and mangers, forms when suitable, thin views, trivial templates.

節錄一些 Gitter 對話


壞範例:

from django.db import models

class Restaurant(models.Model):
    is_fastfood = models.BooleanField()
    average_price = models.IntegerField()

# In view.
some_restaurants = Restaurant.objects.filter(
    is_fastfood=False, average_price__gte=100,
)

好範例:

from django.db import models

class RestaurantQuerySet(models.QuerySet):
    def some(self):
        return self.filter(is_fastfood=False, average_price__gte=100)

class Restaurant(models.Model):
    is_fastfood = models.BooleanField()
    average_price = models.IntegerField()
    objects = RestaurantQuerySet.as_manager()

# In view.
some_restaurants = Restaurant.objects.some()

最主要的好處其實是會讓你的 view 很好讀

找到一個好像還不錯的例子

有個 project 需要找到使用者在一個範圍內能看到的所有 remote 物件

# 一個頁面顯示一個 region
user = request.user
if not user.is_authenticated():
    remotes = Remotes.objects.none()
else:
    remotes = Remote.objects.filter(
        location__isnull=False,
        location__contained=region.boundary,
    )
    if not user.is_superuser:
        remotes = remotes.filter(owner_set__in=[user])

邏輯大概像這樣

我們把測試使用者權限的程式抽出來

class OwnableQuerySet(QuerySet):
    def get_viewable(self, user):
        # Exclude objects not owned by user unless the user is a superuser.
        if not user.is_authenticated():
            return self.none()
        elif user.is_superuser:
            return self.all()
        return self.filter(owner_set__in=[user])

然後把找 region 中 remotes 的程式抽出來

class Region(models.Model):
    # ...
    def get_visible_remotes(self, user):
        remotes = Remote.objects.filter(
            location__isnull=False,
            location__contained=self.boundary,
        )
        remotes = remotes.get_viewable(user=user)
        return remotes

最後在 view 裡就變成這樣:

# 一個頁面顯示一個 region
remotes = region.get_visible_remotes(user=request.user)
uranusjr commented 9 years ago

突然發現 get_visible_remotes 好像有改進空間

class Region(models.Model):
    # ...
    def get_contained_remotes(self):
        return Remote.objects.filter(
            location__isnull=False,
            location__contained=self.boundary,
        )

    def get_visible_remotes(self, user):
        remotes = self.get_contained_remotes().get_viewable(user=user)
        return remotes

Well.

cropse commented 7 years ago

直接繼承Manager這樣做應該也可以? 還是繼承QuerySet下來有別的好處?



class RestaurantManager(models.Manager):
    def some(self):
        return super(RestaurantQuerySet,self).filter(is_fastfood=False, average_price__gte=100)

class Restaurant(models.Model):
    is_fastfood = models.BooleanField()
    average_price = models.IntegerField()
    objects = RestaurantManager()

# In view.
some_restaurants = Restaurant.objects.some()
uranusjr commented 7 years ago

繼承 queryset 有個好處是串其他的條件比較方便。像這樣:

Restaurant.objects.filter_fastfood().filter_cheap()

就一定要把至少把其中一個定義在 QuerySet。所以為了方便起見,這種東西通常都是定義在 QuerySet,然後再用 as_manager()Manager.from_queryset() 建立對應的 manager class。

cropse commented 7 years ago

了解,沒想到可以這樣用。