TAGS :Viewed: 9 - Published at: a few seconds ago

[ simplify this django query ]

I have a Post object that can contact PostTypes of 'Articles' or 'Organization'. Posts have a field called approved, a field called published and there is also a history that keep track of all the previous versions of the Post. I want to get the latest approved and published Posts.

class Post(Published, models.Model):
    headline = models.CharField(max_length=255)
    posttype = models.ForeignKey(PostType)
    organization = models.ManyToManyField('self', null=True,blank=True)
    history = HistoricalRecords() # django-simple-history   
    status = models.IntegerField(
        default=settings.DRAFT_STATUS, # or can be settings.PUBLISHED_STATUS

class PostType(models.Model):
    slug = models.CharField(max_length=32)
    name = models.CharField(max_length=15)

Here is a pictorial representation:


Here is my convoluted way of getting the latest approved and published Posts.

  def get_context_data(self, **kwargs):
    context = super(ArticleCreate, self).get_context_data(**kwargs)

    # Get me all the posttypes except for articles
    exclude_articles = PostType.objects.exclude(slug__icontains="article")

    # get me approved organizations (i.e. not articles) that are approved
    orgs = Post.objects.filter(approved = True).filter(posttype__in=exclude_articles)
    result = []

    # for each of the organization, get me the latest published history
    for org in orgs:

    context['form'].fields['organization'].queryset = result
    return context

The above way of doing this return a list instead of queryset (which is another problem). Can we simplify this and get a queryset?

Answer 1

Now, I haven't tried this refactoring so take it and use it as a pointer!

First, you don't need to chain the .filter() calls. You can "just" use CSV style in one filter call that and using the Q object then you can do a negated query in your filter.

Then we can move on to using relationships, now I haven't used Django Simple History, but i'm guessing it works with normal FK traversal.

So that would mean that you could access the history status via a normal history__status accessor and lastly, using the .values() method will allow you to get a list back with just 1 or several values directly from the DB thus avoiding doing the res.append(org[0].something).

So, as I said, I haven't tried this code out but it should give you a pointer in the right direction.

def get_context_data(self, **kwargs):
    context = super(ArticleCreate, self).get_context_data(**kwargs)

    result = Post.objects.filter(approved = True, ~Q(posttype__slug_icontains="article"), \
                                history__status=settings.PUBLISHED_STATUS) \

    context['form'].fields['organization'].queryset = result
    return context

Now this makes no sense whatsoever, and it's really hard to read so I would suggest that you move this to the Posts model manager.

class PostManager(models.ModelManager):

    def approved_and_published_posts(self):
        return self.get_queryset().filter(approved = True, ~Q(posttype__slug_icontains="article"), \
        history__status=settings.PUBLISHED_STATUS) \

class Post(models.Model):
    objects = PostManager()

Then in your view you could always do this:

orgs = Post.objects.approved_and_published_posts()

Hope this helps and at least points you in the right direction!