【问题标题】:How to generate feed from different models in Django?如何从 Django 中的不同模型生成提要?
【发布时间】:2018-06-09 03:49:37
【问题描述】:

所以,我有两个模型,分别称为公寓和工作。分别显示两个模型的内容很容易,但我想不通的是如何根据日期显示两个模型的混合提要。

jobs = Job.objects.all().order_by('-posted_on')
apartments = Apartment.objects.all().order_by('-date')

职位的发布日期由“posted_by”表示,公寓的发布日期由“日期”表示。如何将这两者结合起来并根据发布的日期对它们进行排序?我尝试以更简单的方式组合这两种模型,例如:

new_feed = list(jobs) + list(apartments)

这只是创建了这两个模型的列表,但它们不是根据日期排列的。

【问题讨论】:

    标签: python django


    【解决方案1】:

    我建议两种方法来实现这一点。

    union() Django 1.11 中的新功能。
    使用 SQL 的 UNION 运算符来组合两个或多个 QuerySet 的结果

    您需要确保有序字段的名称是唯一的 像date 字段为jobapartment

    jobs = Job.objects.all().order_by('-posted_on')
    apartments = Apartment.objects.all().order_by('-date')
    
    new_feed = jobs.union(apartments).order_by('-date')
    

    注意使用此选项,您需要具有相同的字段名称才能对其进行排序。

    或者
    使用chain(),用于将连续序列视为单个序列,使用sorted()lambda对其进行排序

    from itertools import chain
    
    # remove the order_by() in each queryset, use it once with sorted
    jobs = Job.objects.all()
    apartments = Apartment.objects.all()
    result_list = sorted(chain(job, apartments),
                    key=lambda instance: instance.date)
    
    

    使用此选项,您实际上不需要重命名或更改其中一个字段名称,只需添加一个属性方法,让我们选择作业模型

    class Job(models.Model):
        ''' fields '''
        posted_on = models.DateField(......)
    
        @property
        def date(self):
             return self.posted_on
    

    所以现在,你的两个模型都有属性date,你可以使用chain()

    result_list = sorted(chain(job, apartments),
                         key=lambda instance: instance.date)

    【讨论】:

    • 所以我需要将posted_on更改为最新吗?
    • 字段名相同很重要,这样Django过滤和排序都不会麻烦
    • @BibekBhandari 否,如果这种排序将是这样做的唯一原因。您可以在不向 Job 模型中引入人工日期字段的情况下解决您的问题
    • 您可以使用参数reversekey=lambda instance: instance.date, reverse=True 一样反转查询集
    • 对不起,不是reversed,而是reverse,打错了
    【解决方案2】:

    一个很好的方法是使用适配器设计模式。这个想法是我们引入了一种辅助数据结构,可用于对这些模型对象进行排序。与尝试使两个模型都具有用于排序的同名属性相比,此方法有几个好处。最重要的是更改不会影响代码库中的任何其他代码。

    首先,您可以按原样获取对象,但不必按排序方式获取它们,您可以按任意顺序获取所有对象。您也可以按排序顺序仅获取前 100 个。只需在此处获取符合您要求的内容:

    jobs = Job.objects.all()
    apartments = Apartment.objects.all()
    

    然后,我们构建一个元组的辅助列表(用于排序的属性,对象),所以:

    auxiliary_list = ([(job.posted_on, job) for job in jobs] 
                    + [(apartment.date, apartment) for apartment in apartments])
    

    现在,是时候进行排序了。我们将对这个辅助列表进行排序。默认情况下,python sort() 方法按字典顺序对元组进行排序,这意味着它将使用元组的第一个元素,即posted_on 和日期属性进行排序。参数 reverse 设置为 True 以按降序排序,即您希望它们在您的提要中。

    auxiliary_list.sort(reverse=True)
    

    现在,是时候只返回已排序元组的第二个元素了:

    sorted_feed = [obj for _, obj in auxiliary_list]
    

    请记住,如果您希望您的提要很大,那么在内存中对这些元素进行排序并不是最好的方法,但我想这不是您关心的问题。

    【讨论】:

    • 会很好用,但不是最好的更快的方法,这个for job in jobs 需要更多内存,还有这个for apartment in apartments。然后你对它们进行排序,最后你再次遍历它们。
    • 很好的解释。谢谢你。当我使用 django 语言模板在前端显示数据时,你能告诉我如何从 sorted_feed 中知道数据与哪个模型相关
    • 同意,但是为什么要在union()chain() 存在的情况下进行这项艰苦的工作?我永远不会建议使用 for 循环遍历查询集。
    • @Lemayzeur 我也是效率的忠实拥护者,但如果这些列表对于标准网站标准来说不是非常大,我发现拥有干净的代码更为重要,并且这种方法隐藏了所有排序代码负担,并且不需要对现有代码进行任何更改。我回答假设这些对象列表没有超过数十万个项目。此外,使用迭代器而不是列表和索引而不是元组中的对象可以使我提供的代码更高效。为了解释和可读性,我使用了最简单的结构
    • @BibekBhandari 你可以使用python内置的isinstance方法进行测试docs.python.org/2/library/functions.html#isinstance
    【解决方案3】:

    我通过以下方式实现了这一点。 我必须将Video 模型和Article 模型作为提要进行策划。我制作了另一个名为 Post 的模型,然后从 VideoArticle 获得了 OneToOne 密钥。

    # apps.feeds.models.py
    from model_utils.models import TimeStampedModel
    
    class Post(TimeStampedModel):
        ...
        
        @cached_property
        def target(self):
            if getattr(self, "video", None) is not None:
                return self.video
            if getattr(self, "article", None) is not None:
                return self.article
            return None
    
    
    # apps/videos/models.py
    class Video(models.Model):
        post = models.OneToOneField(
            "feeds.Post",
            on_delete=models.CASCADE,
        )
        ...
    
    # apps.articles.models.py
    class Article(models.Model):
        post = models.OneToOneField(
            "feeds.Post",
            on_delete=models.CASCADE,
        )
        ...
    

    然后对于提要 API,我使用 django-rest-framework 对 Post 查询集创建的时间戳进行排序。我自定义了序列化程序的方法并为自定义等添加了查询集注释。这样我就能够从相关的Post 实例中获取ArticleVideo 的数据作为嵌套字典。 此实现的优点是您可以使用注释、select_related、prefetch_related 方法轻松优化查询,这些方法在 Post 查询集上运行良好。

    # apps.feeds.serializers.py
    
    class FeedSerializer(serializers.ModelSerializer):
        type = serializers.SerializerMethodField()
    
        class Meta:
            model = Post
            fields = ("type",)
    
    
        def to_representation(self, instance) -> OrderedDict:
            ret = super().to_representation(instance)
    
            if isinstance(instance.target, Video):
                ret["data"] = VideoSerializer(
                    instance.target, context={"request": self.context["request"]}
                ).data
            else:
                ret["data"] = ArticleSerializer(
                    instance.target, context={"request": self.context["request"]}
                ).data
            return ret
    
        def get_type(self, obj):
            return obj.target._meta.model_name
    
        @staticmethod
        def setup_eager_loading(qs):
            """
            Inspired by:
            http://ses4j.github.io/2015/11/23/optimizing-slow-django-rest-framework-performance/
            """
            qs = qs.select_related("live", "article")
            # other db optimizations...
            ...
            return qs
    
    # apps.feeds.viewsets.py
    class FeedViewSet(viewsets.ModelViewSet):
        queryset = Post.objects.all()
        serializer_class = FeedSerializer
        permission_classes = (IsAuthenticatedOrReadOnly,)
    
        def get_queryset(self):
            qs = super().get_queryset()
            qs = self.serializer_class().setup_eager_loading(qs)
            return as
        
        ...
    
    

    【讨论】:

      猜你喜欢
      • 2011-11-29
      • 2011-01-16
      • 2023-03-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-17
      • 2012-07-01
      相关资源
      最近更新 更多