Подтвердить что ты не робот

Отображать объекты из разных моделей на одной странице в соответствии с их опубликованной датой

У меня есть три разных модели для моего приложения. Все работают так, как я ожидал.

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)

Теперь я показываю каждую из них на отдельной странице для каждой модели (например, на странице заданий, я показываю только задания). Но теперь на главной странице я хочу отобразить их в соответствии с опубликованной датой на той же странице. Как я могу отображать разные объекты из разных моделей на одной странице? Я делаю отдельную модель, например. class Post, а затем использовать сигнал для создания нового сообщения всякий раз, когда новый объект создается из Tender или Job или News? Я действительно надеюсь, что есть лучший способ достичь этого. Или я использую наследование нескольких таблиц? Пожалуйста, помогите мне. Спасибо.

Update:

Я не хочу показывать каждый из объектов модели отдельно на той же странице. Но, как фиды facebook или любые другие социальные сети. Предположим, что в fb все сообщения (будь то изображение, статус, доля) отображаются вместе на домашней странице. Аналогично, в моем случае предположим, что был создан новый объект Job, и после этого создается новый объект News. Затем я хочу сначала показать объект News, а затем объект Job и т.д.

4b9b3361

Ответ 1

Рабочее решение

Есть два рабочих решения, два других ответа. Оба они включают три запроса. И вы запрашиваете всю таблицу с помощью .all(). Результаты этих запросов объединены в один список. Если в каждой из ваших таблиц записано около 10 тыс. Записей, это наложит огромную нагрузку как на ваш сервер 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 теперь индексируется (см. новую опубликованную модель публикации I). Это делает запрос очень быстрым. Итерации по всем записям в python нет.

Как вы узнаете, что именно в шаблоне? Что-то вроде этого.

{% if post.tender %}

{% else %} 
   {% if post.news %}
   {% else %}
{% else %}

Дальнейшая оптимизация

В вашем дизайне есть место для нормализации базы данных. Например, вероятно, что одна и та же компания может опубликовать несколько рабочих мест или тендеров. Как таковая модель компании может пригодиться.

Еще более эффективное решение.

Как насчет одного без многостраничного наследования или нескольких запросов к базе данных? Как насчет решения, в котором вы могли бы даже устранить накладные расходы на рендеринг каждого отдельного элемента?

Это происходит с любезностью redis sorted sets. Каждый раз, когда вы сохраняете объект Post, Job или News, вы добавляете его в отсортированный набор 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

Явное преимущество этого метода заключается в том, что вам вообще не нужны какие-либо запросы к базе данных, и ваши модели по-прежнему очень просты + вы получаете ловушку, брошенную в микс. Если вы находитесь в твиттере, ваша временная шкала, вероятно, создается с помощью механизма, подобного этому.

Ответ 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 %}

При этом вы сохраняете дополнительные накладные расходы на аннотации или должны изменять свои модели.

Ответ 3

Прямым способом является использование chain в сочетании с отсортированным:

Вид

# 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>

Бонус - объединить объекты с разными именами полей

Иногда может потребоваться создание таких каналов с существующими/сторонними моделями. В этом случае у вас нет одинакового имени поля для всех моделей для сортировки.

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)

Ответ 4

Я делаю отдельную модель, например. class Post, а затем использовать сигнал для создавать новую запись, когда новый объект создается из Тендера, или Работа или новости? Я действительно надеюсь, что есть лучший способ достичь этого. Или использовать множительное наследование?

Я не хочу показывать каждый из объектов модели отдельно в одном и том же стр. Но, как каналы Facebook или других социальных сетей.

Я лично не вижу ничего плохого в использовании другой модели, IMHO даже предпочтительнее использовать другую модель, особенно когда есть приложение для что.

Почему? Потому что я никогда не захочу переписать код для чего-то, что может быть достигнуто с помощью расширения моего текущего кода. Вы переучиваете эту проблему, и если не сейчас, вы будете страдать позже.

Ответ 5

Альтернативным решением будет использование haystack Django:

Позволяет выполнять поиск по несвязанным моделям. Это больше, чем другие решения, но эффективные (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')

Затем, каждый раз, когда создается новая модель, вы также создаете сообщение и связываете его с Тендером/Работой/Новостями. Вы должны относить каждое сообщение только к одной из трех моделей.

Создайте сериализатор для Post с отступом сериализаторов для тендера, задания и новостей.

Извините за короткий ответ. Если вы считаете, что это может сработать для вашей проблемы, я напишу позже.