I forgot to mention that I knew about has_unreads. I'm looking for a version of that that doesn't require a topic as an argument; I need to know if the user has any unread posts in the entire forum.
As for the unread tracking, I'd keep it simple:
user = models.ForeignKey(User)
post = models.ForeignKey(Post)
unique_together = ('user', 'post')
Doing it this way would allow for querying the unread posts directly without having to decode a JSON string first.
(Edit: playing around with formatting. Can't figure out how to get the code to indent.)
Edited again: think I figured out formatting. But I also think that may have been what you mean when you mentioned m2m fields. Did you already try this? Did you hit performance issues or something?