djangobb
changeset 156:ddfffe696aa8
replacing Read model by PostTracking and changing algorithm for more correct
| author | slav0nic <slav0nic0@gmail.com> |
|---|---|
| date | Thu Oct 22 15:19:26 2009 +0300 (9 months ago) |
| parents | acfd250e5f8c |
| children | afe6455bc6c0 92fb0cc7f0a6 c38d73dc5013 |
| files | apps/forum/admin.py apps/forum/fields.py apps/forum/models.py apps/forum/templates/forum/forum.html apps/forum/templatetags/forum_extras.py apps/forum/unread.py apps/forum/views.py |
line diff
1.1 --- a/apps/forum/admin.py Wed Oct 21 14:28:14 2009 +0300 1.2 +++ b/apps/forum/admin.py Thu Oct 22 15:19:26 2009 +0300 1.3 @@ -1,8 +1,8 @@ 1.4 # -*- coding: utf-8 -*- 1.5 from django.contrib import admin 1.6 1.7 -from forum.models import Category, Forum, Topic, Post, Profile, Read,\ 1.8 - Reputation, Report, Ban 1.9 +from forum.models import Category, Forum, Topic, Post, Profile, Reputation,\ 1.10 + Report, Ban 1.11 1.12 1.13 class CategoryAdmin(admin.ModelAdmin): 1.14 @@ -25,10 +25,6 @@ 1.15 list_display = ['user', 'status', 'time_zone', 'location', 'language'] 1.16 raw_id_fields = ['user'] 1.17 1.18 -class ReadAdmin(admin.ModelAdmin): 1.19 - list_display = ['user', 'topic', 'time'] 1.20 - raw_id_fields = ['user', 'topic'] 1.21 - 1.22 class ReputationAdmin(admin.ModelAdmin): 1.23 list_display = ['from_user', 'to_user', 'topic', 'sign', 'time', 'reason'] 1.24 raw_id_fields = ['from_user', 'to_user', 'topic'] 1.25 @@ -47,7 +43,6 @@ 1.26 admin.site.register(Topic, TopicAdmin) 1.27 admin.site.register(Post, PostAdmin) 1.28 admin.site.register(Profile, ProfileAdmin) 1.29 -admin.site.register(Read, ReadAdmin) 1.30 admin.site.register(Reputation, ReputationAdmin) 1.31 admin.site.register(Report, ReportAdmin) 1.32 admin.site.register(Ban, BanAdmin)
2.1 --- a/apps/forum/fields.py Wed Oct 21 14:28:14 2009 +0300 2.2 +++ b/apps/forum/fields.py Thu Oct 22 15:19:26 2009 +0300 2.3 @@ -7,15 +7,13 @@ 2.4 except ImportError: 2.5 from StringIO import StringIO 2.6 import logging 2.7 -try: 2.8 - import cPickle as pickle 2.9 -except ImportError: 2.10 - import Pickle as pickle 2.11 2.12 from django.db.models import OneToOneField 2.13 from django.db.models.fields.related import SingleRelatedObjectDescriptor 2.14 from django.db import models 2.15 from django.core.files.uploadedfile import SimpleUploadedFile 2.16 +from django.core.serializers.json import DjangoJSONEncoder 2.17 +from django.utils import simplejson as json 2.18 2.19 2.20 class AutoSingleRelatedObjectDescriptor(SingleRelatedObjectDescriptor): 2.21 @@ -83,19 +81,30 @@ 2.22 return string.getvalue() 2.23 2.24 2.25 -class RangesField(models.TextField): 2.26 +class JSONField(models.TextField): 2.27 """ 2.28 - Field for stored pickled data. 2.29 - Taken from Cicero forum engine. 2.30 + JSONField is a generic textfield that neatly serializes/unserializes 2.31 + JSON objects seamlessly. 2.32 + Django snippet #1478 2.33 """ 2.34 + 2.35 __metaclass__ = models.SubfieldBase 2.36 2.37 def to_python(self, value): 2.38 - if isinstance(value, list): 2.39 - return value 2.40 - if not value: 2.41 - return [(0, 0)] 2.42 - return pickle.loads(str(value)) 2.43 + if value == "": 2.44 + return None 2.45 2.46 - def get_db_prep_value(self, value): 2.47 - return unicode(pickle.dumps(value)) 2.48 + try: 2.49 + if isinstance(value, basestring): 2.50 + return json.loads(value) 2.51 + except ValueError: 2.52 + pass 2.53 + return value 2.54 + 2.55 + def get_db_prep_save(self, value): 2.56 + if value == "": 2.57 + return None 2.58 + if isinstance(value, dict): 2.59 + value = json.dumps(value, cls=DjangoJSONEncoder) 2.60 + return super(JSONField, self).get_db_prep_save(value) 2.61 +
3.1 --- a/apps/forum/models.py Wed Oct 21 14:28:14 2009 +0300 3.2 +++ b/apps/forum/models.py Thu Oct 22 15:19:26 2009 +0300 3.3 @@ -14,7 +14,7 @@ 3.4 from markdown import Markdown 3.5 3.6 from forum.markups import mypostmarkup 3.7 -from forum.fields import AutoOneToOneField, ExtendedImageField, RangesField 3.8 +from forum.fields import AutoOneToOneField, ExtendedImageField, JSONField 3.9 from forum.util import urlize, smiles 3.10 from forum import settings as forum_settings 3.11 3.12 @@ -156,18 +156,24 @@ 3.13 super(Topic, self).save(*args, **kwargs) 3.14 3.15 def update_read(self, user): 3.16 - read, new = Read.objects.get_or_create(user=user, topic=self) 3.17 - if not new: 3.18 - read.time = datetime.now() 3.19 - read.save() 3.20 - 3.21 - #def has_unreads(self, user): 3.22 - #try: 3.23 - #read = Read.objects.get(user=user, topic=self) 3.24 - #except Read.DoesNotExist: 3.25 - #return True 3.26 - #else: 3.27 - #return self.updated > read.time 3.28 + tracking = user.posttracking 3.29 + #if last_read > last_read - don't check topics 3.30 + if tracking.last_read and (tracking.last_read > self.last_post.created): 3.31 + return 3.32 + if isinstance(tracking.topics, dict): 3.33 + #clear topics if len > 5Kb and set last_read to current time 3.34 + if len(tracking.topics) > 5120: 3.35 + tracking.topics = None 3.36 + tracking.last_read = datetime.now() 3.37 + tracking.save() 3.38 + #update topics if exist new post or does't exist in dict 3.39 + if self.last_post.id > tracking.topics.get(str(self.id), 0): 3.40 + tracking.topics.setdefault(str(self.id), self.last_post.id) 3.41 + tracking.save() 3.42 + else: 3.43 + #initialize topic tracking dict 3.44 + tracking.topics = {self.id: self.last_post.id} 3.45 + tracking.save() 3.46 3.47 3.48 class Post(models.Model): 3.49 @@ -241,7 +247,6 @@ 3.50 forum.save() 3.51 3.52 3.53 - 3.54 class Reputation(models.Model): 3.55 from_user = models.ForeignKey(User, related_name='reputations_from', verbose_name=_('From')) 3.56 to_user = models.ForeignKey(User, related_name='reputations_to', verbose_name=_('To')) 3.57 @@ -249,7 +254,7 @@ 3.58 time = models.DateTimeField(_('Time'), blank=True) 3.59 sign = models.IntegerField(_('Sign'), choices=SIGN_CHOICES, default=0) 3.60 reason = models.TextField(_('Reason'), blank=True, default='', max_length=1000) 3.61 - 3.62 + 3.63 class Meta: 3.64 verbose_name = _('Reputation') 3.65 verbose_name_plural = _('Reputations') 3.66 @@ -278,7 +283,6 @@ 3.67 privacy_permission = models.IntegerField(_('Privacy permission'), choices=PRIVACY_CHOICES, default=1) 3.68 markup = models.CharField(_('Default markup'), max_length=15, default=forum_settings.DEFAULT_MARKUP, choices=MARKUP_CHOICES) 3.69 post_count = models.IntegerField(_('Post count'), blank=True, default=0) 3.70 - #read_posts = fields.RangesField(editable=False) 3.71 3.72 class Meta: 3.73 verbose_name = _('Profile') 3.74 @@ -298,28 +302,22 @@ 3.75 return Reputation.objects.filter(to_user=self.user, sign=1).count() 3.76 3.77 3.78 -class Read(models.Model): 3.79 +class PostTracking(models.Model): 3.80 """ 3.81 - For each topic that user has entered the time 3.82 - is logged to this model. 3.83 + Model for tracking read/unread posts. 3.84 + In topics stored ids of topics and last_posts as dict. 3.85 """ 3.86 3.87 - user = models.ForeignKey(User, verbose_name=_('User')) 3.88 - topic = models.ForeignKey(Topic, verbose_name=_('Topic')) 3.89 - time = models.DateTimeField(_('Time'), blank=True) 3.90 + user = AutoOneToOneField(User) 3.91 + topics = JSONField(null=True) 3.92 + last_read = models.DateTimeField(null=True) 3.93 3.94 class Meta: 3.95 - unique_together = [('user', 'topic')] 3.96 - verbose_name = _('Read') 3.97 - verbose_name_plural = _('Reads') 3.98 - 3.99 - def save(self, *args, **kwargs): 3.100 - if self.time is None: 3.101 - self.time = datetime.now() 3.102 - super(Read, self).save(*args, **kwargs) 3.103 + verbose_name = _('Post tracking') 3.104 + verbose_name_plural = _('Post tracking') 3.105 3.106 def __unicode__(self): 3.107 - return u'T[%d], U[%d]: %s' % (self.topic.id, self.user.id, unicode(self.time)) 3.108 + return self.user.username 3.109 3.110 3.111 class Report(models.Model):
4.1 --- a/apps/forum/templates/forum/forum.html Wed Oct 21 14:28:14 2009 +0300 4.2 +++ b/apps/forum/templates/forum/forum.html Thu Oct 22 15:19:26 2009 +0300 4.3 @@ -32,7 +32,7 @@ 4.4 </thead> 4.5 <tbody> 4.6 {% if topics %} 4.7 - {% for topic in topics|forum_unreads:user %} 4.8 + {% for topic in topics %} 4.9 <tr> 4.10 <td class="tcl"> 4.11 <div class="intd">
5.1 --- a/apps/forum/templatetags/forum_extras.py Wed Oct 21 14:28:14 2009 +0300 5.2 +++ b/apps/forum/templatetags/forum_extras.py Thu Oct 22 15:19:26 2009 +0300 5.3 @@ -14,8 +14,7 @@ 5.4 from django.utils import dateformat 5.5 from django.utils.hashcompat import md5_constructor 5.6 5.7 -from forum.models import Forum, Topic, Post, Read, PrivateMessage, Report 5.8 -from forum.unread import cache_unreads 5.9 +from forum.models import Forum, Topic, Post, PostTracking, PrivateMessage, Report 5.10 from forum import settings as forum_settings 5.11 5.12 register = template.Library() 5.13 @@ -100,7 +99,8 @@ 5.14 'page_list': page_list, 5.15 'per_page': context['per_page'], 5.16 } 5.17 - 5.18 + 5.19 + 5.20 @register.inclusion_tag('forum/lofi/pagination.html',takes_context=True) 5.21 def lofi_pagination(context): 5.22 page_list = range(1, context['pages'] + 1) 5.23 @@ -119,6 +119,7 @@ 5.24 'paginator': paginator, 5.25 } 5.26 5.27 + 5.28 @register.simple_tag 5.29 def link(object, anchor=u''): 5.30 """ 5.31 @@ -129,6 +130,7 @@ 5.32 anchor = anchor or smart_unicode(object) 5.33 return mark_safe('<a href="%s">%s</a>' % (url, escape(anchor))) 5.34 5.35 + 5.36 @register.simple_tag 5.37 def lofi_link(object, anchor=u''): 5.38 """ 5.39 @@ -139,36 +141,23 @@ 5.40 anchor = anchor or smart_unicode(object) 5.41 return mark_safe('<a href="%slofi">%s</a>' % (url, escape(anchor))) 5.42 5.43 + 5.44 @register.filter 5.45 def has_unreads(topic, user): 5.46 """ 5.47 Check if topic has messages which user didn't read. 5.48 """ 5.49 - 5.50 - now = datetime.now() 5.51 - delta = timedelta(seconds=forum_settings.READ_TIMEOUT) 5.52 - 5.53 - if not user.is_authenticated(): 5.54 - return False 5.55 + if not user.is_authenticated() or\ 5.56 + (user.posttracking.last_read is not None and\ 5.57 + user.posttracking.last_read > topic.last_post.created): 5.58 + return False 5.59 else: 5.60 - if isinstance(topic, Topic): 5.61 - if (now - delta > topic.updated): 5.62 + if isinstance(user.posttracking.topics, dict): 5.63 + if topic.last_post.id > user.posttracking.topics.get(str(topic.id), 0): 5.64 + return True 5.65 + else: 5.66 return False 5.67 - else: 5.68 - if hasattr(topic, '_read'): 5.69 - read = topic._read 5.70 - else: 5.71 - try: 5.72 - read = Read.objects.get(user=user, topic=topic) 5.73 - except Read.DoesNotExist: 5.74 - read = None 5.75 - 5.76 - if read is None: 5.77 - return True 5.78 - else: 5.79 - return topic.updated > read.time 5.80 - else: 5.81 - raise Exception('Object should be a topic') 5.82 + return True 5.83 5.84 5.85 @register.filter 5.86 @@ -219,11 +208,6 @@ 5.87 5.88 5.89 @register.filter 5.90 -def forum_unreads(qs, user): 5.91 - return cache_unreads(qs, user) 5.92 - 5.93 - 5.94 -@register.filter 5.95 def forum_authority(user): 5.96 posts = user.forum_profile.post_count 5.97 if posts >= forum_settings.AUTHORITY_STEP_10:
6.1 --- a/apps/forum/unread.py Wed Oct 21 14:28:14 2009 +0300 6.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 6.3 @@ -1,23 +0,0 @@ 6.4 -from forum.models import Topic, Post, Read 6.5 - 6.6 -def cache_unreads(qs, user): 6.7 - if not len(qs) or not user.is_authenticated(): 6.8 - return qs 6.9 - if isinstance(qs[0], Topic): 6.10 - reads = Read.objects.filter(topic__pk__in=set(x.id for x in qs), 6.11 - user=user).select_related() 6.12 - read_map = dict((x.topic.id, x) for x in reads) 6.13 - 6.14 - for topic in qs: 6.15 - topic._read = read_map.get(topic.id, None) 6.16 - return qs 6.17 - elif isinstance(qs[0], Post): 6.18 - ids = set(x.topic.id for x in qs) 6.19 - reads = Read.objects.filter(topic__pk__in=ids, user=user).select_related() 6.20 - read_map = dict((x.topic.id, x) for x in reads) 6.21 - 6.22 - for post in qs: 6.23 - post.topic._read = read_map.get(post.topic.id, None) 6.24 - return qs 6.25 - else: 6.26 - raise Exception('cache_unreads could process only Post or Topic querysets')
7.1 --- a/apps/forum/views.py Wed Oct 21 14:28:14 2009 +0300 7.2 +++ b/apps/forum/views.py Thu Oct 22 15:19:26 2009 +0300 7.3 @@ -1,6 +1,6 @@ 7.4 import math 7.5 import re 7.6 -import datetime 7.7 +from datetime import datetime, timedelta 7.8 7.9 from django.shortcuts import get_object_or_404 7.10 from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseForbidden 7.11 @@ -15,8 +15,8 @@ 7.12 7.13 7.14 from forum.util import render_to, paged, build_form, paginate, set_language 7.15 -from forum.models import Category, Forum, Topic, Post, Profile, Read,\ 7.16 - Reputation, Report, PrivateMessage, Attachment 7.17 +from forum.models import Category, Forum, Topic, Post, Profile, Reputation,\ 7.18 + Report, PrivateMessage, Attachment, PostTracking 7.19 from forum.forms import AddPostForm, EditPostForm, UserSearchForm,\ 7.20 PostSearchForm, ReputationForm, MailToForm, EssentialsProfileForm,\ 7.21 PersonalProfileForm, MessagingProfileForm, PersonalityProfileForm,\ 7.22 @@ -129,10 +129,11 @@ 7.23 if 'action' in request.GET: 7.24 action = request.GET['action'] 7.25 if action == 'show_24h': 7.26 - date = datetime.datetime.today() - datetime.timedelta(1) 7.27 + date = datetime.today() - timedelta(1) 7.28 topics = Topic.objects.filter(created__gte=date).order_by('created') 7.29 elif action == 'show_new': 7.30 #TODO: FIXME 7.31 + #must be filter topic.last_post > tracking.last_read and exclude tracking.topics 7.32 topics = Topic.objects.all().order_by('created') 7.33 topics = [topic for topic in topics if forum_extras.has_unreads(topic, request.user)] 7.34 elif action == 'show_unanswered': 7.35 @@ -224,14 +225,10 @@ 7.36 if 'action' in request.GET: 7.37 action = request.GET['action'] 7.38 if action =='markread': 7.39 - for category in Category.objects.all(): 7.40 - for topic in category.topics: 7.41 - read, new = Read.objects.get_or_create(user=request.user, topic=topic) 7.42 - if not new: 7.43 - read.time = datetime.datetime.now() 7.44 - read.save() 7.45 + user = request.user 7.46 + PostTracking.objects.filter(user=user).update(last_read=datetime.now(), topics=None) 7.47 return HttpResponseRedirect(reverse('index')) 7.48 - 7.49 + 7.50 elif action == 'report': 7.51 if request.GET.get('post_id', ''): 7.52 post_id = request.GET['post_id']
