根据发布日期在同一页面显示来自不同模型的对象

Posted

技术标签:

【中文标题】根据发布日期在同一页面显示来自不同模型的对象【英文标题】:Display objects from different models at the same page according to their published date 【发布时间】:2016-10-11 08:58:06 【问题描述】:

我的应用程序有三种不同的模型。一切都按我的预期工作。

class Tender(models.Model):
    title = models.CharField(max_length=256)
    description = models.TextField()
    department = models.CharField(max_length=50)
    address = models.CharField(max_length=50)
    nature_of_work = models.CharField(choices=WORK_NATURE, max_length=1)
    period_of_completion = models.DateField()
    pubdat = models.DateTimeField(default=timezone.now)    

class Job(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL)
    title = models.CharField(max_length=256)
    qualification = models.CharField(max_length=256)
    interview_type = models.CharField(max_length=2, choices=INTERVIEW_TYPE)
    type_of_job = models.CharField(max_length=1, choices=JOB_TYPE)
    number_of_vacancies = models.IntegerField()
    employer = models.CharField(max_length=50)
    salary = models.IntegerField()    
    pubdat = models.DateTimeField(default=timezone.now)

class News(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL)
    title = models.CharField(max_length=150)
    body = models.TextField()
    pubdat = models.DateTimeField(default=timezone.now)

现在我在每个模型的单独页面上显示它们中的每一个(例如,在 jobs 页面中,我只显示工作。)。但是现在在主页上,我想根据它们在同一页面上的发布日期显示这些。如何在同一页面显示来自不同模型的不同对象?我是否制作一个单独的模型,例如class Post,然后每当从TenderJobNews 创建新对象时使用信号创建新帖子?我真的希望有更好的方法来实现这一点。还是我使用多表继承?请帮我。谢谢。

更新:

我不想在同一页面上分别显示每个模型对象。但就像 facebook 或任何其他社交媒体的提要一样。假设在 fb 中,任何帖子(无论是图片、状态、分享)都一起显示在主页中。同样在我的例子中,假设创建了一个新的 Job 对象,然后创建了一个新的 News 对象。然后,我想先显示 News 对象,然后是 Job 对象,以此类推。

【问题讨论】:

我不太明白这里的问题。您可以在同一页面上显示 N 个模型。您可以根据发布日期查询所有三个模型,并将它们发送回响应并呈现它。 @RajeshYogeshwar 请查看更新 一种方法是为每个模型实例创建一个 json,如果您愿意,可以进行序列化。您也许可以尝试使用相同的密钥。创建此类 json 的列表并将其传递到模板中。这是一种简单的方法,但不是唯一的方法。 如果你真的想要像 facebook 这样的东西,那么不要改变你现有的模型,你想扩展你的应用程序,而不是重写它。我的建议是看看django-activity-stream,使用它你可以实现你所需要的。 【参考方案1】:

可行的解决方案

有两个可行的解决方案另外两个答案。这两个都涉及三个查询。您正在使用.all() 查询整个表。这些查询的结果组合在一起形成一个列表。如果您的每个表都有大约 10k 条记录,这将对您的 wsgi 服务器和数据库造成巨大压力。即使每个表每个只有 100 条记录,您也可以在视图中不必要地循环 300 次。 总之反应慢

高效的工作解决方案。

如果您想要一个高效的解决方案,多表继承绝对是正确的方法。您的模型可能如下所示:

class Post(models.Model):
    title = models.CharField(max_length=256)
    description = models.TextField()
    pubdat = models.DateTimeField(default=timezone.now, db_index = True)    

class Tender(Post):
    department = models.CharField(max_length=50)
    address = models.CharField(max_length=50)
    nature_of_work = models.CharField(choices=WORK_NATURE, max_length=1)
    period_of_completion = models.DateField()

class Job(Post):
    user = models.ForeignKey(settings.AUTH_USER_MODEL)
    qualification = models.CharField(max_length=256)
    interview_type = models.CharField(max_length=2, choices=INTERVIEW_TYPE)
    type_of_job = models.CharField(max_length=1, choices=JOB_TYPE)
    number_of_vacancies = models.IntegerField()
    employer = models.CharField(max_length=50)
    salary = models.IntegerField()    


class News(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL)

    def _get_body(self):
        return self.description

    body = property(_get_body)

现在您的查询很简单

 Post.objects.select_related(
   'job','tender','news').all().order_by('-pubdat')   # you really should slice

pubdat 字段现在已编入索引(请参阅我发布的新 Post 模型)。这使得查询非常快。 python中的所有记录都没有迭代。

如何找出模板中的哪个是哪个?像这样的东西。

% if post.tender %

% else % 
   % if post.news %
   % else %
% else %

进一步优化

您的设计中有一些空间可以规范化数据库。例如,同一家公司可能会发布多个职位或投标。因此,这样的公司模式可能会派上用场。

更有效的解决方案。

如果没有多表继承或多数据库查询呢?您甚至可以消除渲染每个单独项目的开销的解决方案怎么样?

感谢redis sorted sets。每次保存 PostJobNews 对象时,都会将其添加到 redis 排序集中。

from django.db.models.signals import pre_delete, post_save
from django.forms.models import model_to_dict

@receiver(post_save, sender=News)
@receiver(post_save, sender=Post)
@receiver(post_save, sender=Job)

def add_to_redis(sender, instance, **kwargs):
    rdb = redis.Redis()

    #instead of adding the instance, you can consider adding the 
    #rendered html, that ought to save you a few more CPU cycles.

    rdb.zadd(key, instance.pubdat, model_to_dict(instance)

    if (rdb.zcard > 100) : # choose a suitable number
         rdb.zremrangebyrank(key, 0, 100)

同样,你需要添加一个 pre_delete 来将它们从 redis 中删除

这种方法的明显优势是您根本不需要任何数据库查询,而且您的模型仍然非常简单 + 您会被混杂其中。如果您在 Twitter 上,您的时间线可能是通过类似于此的机制生成的。

【讨论】:

模板中的% if post.tender %% if post.news % 是如何工作的?假设postPost 的一个实例,那么newstender 属性从何而来? 你好@ARJMP 那是因为当你使用多表继承时Django会自动创建一个OneToOneField 此解决方案将触发至少一个额外的查询每个对象来获取子类。 抱歉疏忽,忘记选择相关 是的,我也倾向于多表继承。但后来我看到了这个post。您对此有何看法?【参考方案2】:

以下应该是你需要的。但是为了提高性能,您可以在每个模型中创建一个额外的 type 字段,这样就可以避免使用 annotation

您的视图将类似于:

from django.db.models import CharField

def home(request):
    # annotate a type for each model to be used in the template
    tenders = Tender.object.all().annotate(type=Value('tender', CharField()))
    jobs = Job.object.all().annotate(type=Value('job', CharField()))
    news = News.object.all().annotate(type=Value('news', CharField()))

    all_items = list(tenders) + list(jobs) + list(news)

    # all items sorted by publication date. Most recent first
    all_items_feed = sorted(all_items, key=lambda obj: obj.pubdat)

    return render(request, 'home.html', 'all_items_feed': all_items_feed)

在您的模板中,项目按照它们的排序顺序(按新近度)出现,您可以通过区分项目类型为每个项目应用适当的 html 和样式:

# home.html
% for item in all_items_feed %
    % if item.type == 'tender' %
    % comment "html block for tender items" %% endcomment %

    % elif item.type == 'news' %
    % comment "html block for news items" %% endcomment %

    % else %
    % comment "html block for job items" %% endcomment %

    % endif %
% endfor %

您可以完全避免annotation,使用模型对象的__class__属性来区分并将它们放在适当的html块中。

对于Tender 对象,item.__class__ 将是app_name.models.Tender,其中app_name 是包含模型的 Django 应用程序的名称。

因此,如果在 home 视图中不使用注释,您的模板将如下所示:

% for item in all_items_feed %
    % if item.__class__ == 'app_name.models.Tender' %

    % elif item.__class__ == 'app_name.models.News' %
     ...
    % endif %
% endfor %

这样,您可以节省注释或修改模型的额外开销。

【讨论】:

我收到此错误:NameError: name 'Value' is not defined. 你应该导入Value:from django.db.models import Value【参考方案3】:

直接的方法是将chain 与 sorted 结合使用:

查看

# your_app/views.py
from django.shortcuts import render
from itertools import chain
from models import Tender, Job, News

def feed(request):

    object_list = sorted(chain(
        Tender.objects.all(),
        Job.objects.all(),
        News.objects.all()
    ), key=lambda obj: obj.pubdat)

    return render(request, 'feed.html', 'feed': object_list)

请注意 - 上面提到的使用 .all() 的查询集应理解为占位符。与许多条目一样,这可能是一个性能问题。示例代码将首先评估查询集,然后对它们进行排序。多达数百条记录可能不会对性能产生(可衡量的)影响 - 但在有数百万/数十亿条目的情况下,它值得一看。

要在排序之前取一个切片,请使用以下内容:

Tender.objects.all()[:20]

或为您的模型使用自定义 Manager 来卸载逻辑。

class JobManager(models.Manager):
    def featured(self):
        return self.get_query_set().filter(featured=True)

然后你可以使用类似的东西:

Job.objects.featured()

模板

如果根据对象类需要额外的逻辑,创建一个简单的模板标签:

#templatetags/ctype_tags.py
from django import template
register = template.Library()

@register.filter
def ctype(value):
    return value.__class__.__name__.lower()

#templates/feed.html
% load ctype_tags %

<div>
    % for item in feed reversed %
    <p> item|ctype  -  item.title  -  item.pubdat </p>
    % endfor %
</div>

奖励 - 组合具有不同字段名称的对象

有时可能需要使用现有/第 3 方模型创建此类提要。在这种情况下,所有模型的排序依据都不相同。

DATE_FIELD_MAPPING = 
    Tender: 'pubdat',
    Job: 'publish_date',
    News: 'created',


def date_key_mapping(obj):
    return getattr(obj, DATE_FIELD_MAPPING[type(obj)])


def feed(request):
    object_list = sorted(chain(
        Tender.objects.all(),
        Job.objects.all(),
        News.objects.all()
    ), key=date_key_mapping)

【讨论】:

因为,上述解决方案可能是性能问题。假设从您的示例中,我将从我想成为 REST 的 web api 调用 feed 方法。那么,如果我在所有三个模型(例如 Jobs.objects.featured())上切片并使用 featured() 来获取 object_list,它是否仍然是未来的性能问题? @Aamu 不,这不是问题。我只是添加了关于切片的注释,以确保没有人会“意外”使用这种方法并最终加载完整的数据库表。【参考方案4】:

我是否制作一个单独的模型,例如?类 Post ,然后使用信号 每当从 Tender 创建新对象时创建新帖子,或 工作,还是新闻?我真的希望有更好的方法来实现这一点。要么 我应该使用多表继承吗?

我不想同时单独显示每个模型对象 页。但就像 facebook 或任何其他社交媒体的提要一样。

我个人认为使用其他模型没有任何问题,恕我直言,使用其他模型更可取,特别是当有 an app 时。

为什么?因为我永远不想为可以通过扩展我当前的代码来实现的东西重写代码。你过度设计了这个问题,如果不是现在,你以后会受苦。

【讨论】:

我同意您更改任何现有代码的方法。但是我应该引入一个额外的模型(和数据库表等)。只有在不可避免的情况下才会这样做。在此处描述的情况下,您无需对现有模型进行任何修改即可解决它。 ***.com/a/37830107/469111 是的,我同意,您的解决方案可以工作,但正如您所提到的 - 它存在扩展问题,OP 应该非常小心地解决它。实际上,我可以建议的一种解决方法是始终在过去 1/2/3/5/任何天(取决于活动)从每个模型中获取记录。我主要是反对Refactoring Models,做无用的继承等等。 @Todor 谢谢你的建议。我将研究 django-activity-stream。 @Todor 这总是取决于你的情况...... imo“避免过早优化”是一个非常有价值的模式。因此,在具有 【参考方案5】:

另一种解决方案是使用 Django haystack:

http://haystacksearch.org/ http://django-haystack.readthedocs.io/en/v2.4.1/

它允许您搜索不相关的模型。它比其他解决方案工作更多,但效率很高(1 个快速查询),您也可以轻松过滤您的列表。


在您的情况下,您需要在所有搜索索引中定义 pubdate

【讨论】:

【参考方案6】:

我现在无法对其进行测试,但您应该创建一个模型,例如:

class Post(models.Model):

    pubdat = models.DateTimeField(default=timezone.now)
    tender = models.ForeignKey('Tender')
    job = models.ForeignKey('Job')
    news = models.ForeignKey('News')

然后,每次创建新模型时,您也会创建一个帖子并将其与 Tender/Job/News 相关联。您应该仅将每个帖子与三个模型中的一个关联起来。

使用缩进的 Tender、Job 和 News 序列化程序为 Post 创建一个序列化程序。

对不起,简短的回答。如果您认为它可以解决您的问题,我稍后会写更多。

【讨论】:

您应该使用 Generic relations 而不是这种方法的“硬编码”外键。

以上是关于根据发布日期在同一页面显示来自不同模型的对象的主要内容,如果未能解决你的问题,请参考以下文章

在同一页面上使用两个具有两种不同日期格式的日期选择器

html中提交表单后如何在同一页面上显示表格?

有没有办法根据条件在新页面中显示特定的内联?

如何在单独的 html 页面上显示来自提交表单的计算值? [复制]

来自 2 个表的数据未显示在同一页面上

将来自两个不同 wordpress 的帖子合并到一个按日期排序的帖子页面