0

I've build a blog using Django (very minimal functional blog) and I want to be able to show to the user in my blog only the posts he hasn't read yet. I have 10,000+ posts and I want to show the user every time he log in a random posts he has not seen yet/ I am new with Django and I not sure I should do it the right way. I have the Post model:

class Post(models.Model):
author = models.ForeignKey('auth.User',on_delete=models.CASCADE)
title = models.CharField(max_length=200)
text = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
published_date = models.DateTimeField(blank=True, null=True)

def publish(self):
    self.published_date = timezone.now()
    self.save()

def approve_comments(self):
    return self.comments.filter(approved_comment=True)

def get_absolute_url(self):
    return reverse("post_detail",kwargs={'pk':self.pk})

def __str__(self):
    return self.title

Thank you!

Adam
  • 299
  • 1
  • 4
  • 19
  • Where's the relation between `User` and `Post` that shows read posts? Just add that (m2m relationship) and then filter on posts by excluding the ones where the relationship already exists. Then you need to select random posts, which is explained [here](https://stackoverflow.com/questions/3506678/in-django-how-do-i-select-100-random-records-from-the-database) – dirkgroten Feb 06 '20 at 12:22
  • @dirkgroten I did not have that kind of relationship because this is what I asked for - I asked how to make that kind of relationship . Thanks! – Adam Mar 02 '20 at 14:39

2 Answers2

1

You should use ManyToManyField:

class Post(models.Model):
author = models.ForeignKey('auth.User',on_delete=models.CASCADE)
title = models.CharField(max_length=200)
text = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
published_date = models.DateTimeField(blank=True, null=True)
read_users = models.ManyToManyField(User) # Add this line

def publish(self):
    self.published_date = timezone.now()
    self.save()

def approve_comments(self):
    return self.comments.filter(approved_comment=True)

def get_absolute_url(self):
    return reverse("post_detail",kwargs={'pk':self.pk})

def __str__(self):
    return self.title
Furkan Guvenc
  • 108
  • 3
  • 6
1

You have to keep record of user and which posts he has read. Just like this

class UserReadPost(models.Model):
    user = models.ForeignKey(User, releated_name='user_read_posts')
    post = models.ForeignKey(Post, releated_name='posts_read_by_users')
    read_at = models.DateTimeField(auto_now=True)

You will have to add a record in this table every time a user read some post. You can get all posts which user have not read yet like this.

from django.db.models import OuterRef, Subquery, Exists

post_read_status = UserReadPost.objects.filter(post=OuterRef('post'), user=current_user_object)
Post.objects.all().annotate(has_read=Exists(post_read_status)).exclude(has_read=True).order_by('?')

This can be done using ManyToManyField but adding your own table will give you ability to check when that user read that specific post.

Muhammad Hassan
  • 14,086
  • 7
  • 32
  • 54
  • 1
    Note that `order_by('?')` is a really inefficient way to pick random elements. It'll be very slow on large tables. See [this](https://stackoverflow.com/questions/1731346/how-to-get-two-random-records-with-django/6405601#6405601) for a more efficient way. If you want 5 posts, make 5 queries for a random index, this will be faster than the one query with `order_by('?')`. – dirkgroten Feb 06 '20 at 12:26
  • @dirkgroten I just wrote how it can be done. Thanx for enlightenment. – Muhammad Hassan Feb 06 '20 at 12:32