src/cm/client.py
changeset 0 40c8f766c9b8
child 12 f69ff46d3240
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cm/client.py	Mon Nov 23 15:14:29 2009 +0100
@@ -0,0 +1,331 @@
+from django.forms.fields import email_re
+from django.http import HttpResponse, HttpResponseRedirect
+from django.core.exceptions import ObjectDoesNotExist
+from django.forms import ValidationError
+from django.utils import simplejson
+from django.utils.translation import ugettext as _
+from tagging.models import Tag
+from tagging.utils import parse_tag_input, LOGARITHMIC, calculate_cloud
+from tagging.models import TaggedItem
+from cm.models import *
+from cm.utils.timezone import request_tz_convert
+from itertools import groupby
+from time import mktime, sleep
+from cm.converters.pandoc_converters import pandoc_convert
+from cm.security import get_viewable_comments, list_viewable_comments, has_perm, has_perm_on_text, has_perm_on_comment, has_own_perm
+from cm.activity import register_activity
+from cm.utils.date import datetime_to_user_str, datetime_to_epoch
+from cm.cm_settings import AUTO_CONTRIB_REGISTER
+from settings import CLIENT_DATE_FMT
+import re
+import time
+import operator
+
+
+selection_place_error_msg = _(u'A selection is required. Select in the text the part your comment applies to.')
+comment_states = ('approved', 'unapproved', 'pending')
+
+def is_valid_email(email):
+    if email_re.match(email) : 
+        return True 
+    return False
+
+#
+def jsonize(obj, request):
+    return simplejson.dumps(obj, cls=RequestComplexEncoder, request=request)
+
+class RequestComplexEncoder(simplejson.JSONEncoder):
+    def __init__(self, request, **kw):
+        self.request = request
+        simplejson.JSONEncoder.__init__(self, **kw)
+        
+        
+    def default(self, obj):
+        if isinstance(obj, Comment) :
+            comment = obj
+            #replies = list(comment.comment_set.order_by('created'))
+            text=comment.text_version.text
+            replies = get_viewable_comments(self.request, comment.comment_set.all(), text)
+            
+            # can_view == true because of get_viewable_comments filter
+            can_moderate = has_perm(self.request, 'can_edit_comment', text)   
+            can_edit = has_perm(self.request, 'can_edit_comment', text) or has_own_perm(self.request, 'can_edit_comment_own', text, comment)  
+            can_delete = has_perm(self.request, 'can_delete_comment', text) or has_own_perm(self.request, 'can_delete_comment_own', text, comment)
+            
+            return {'id' : comment.id, 
+                    'key' : comment.key,
+                   'created_user_str' : datetime_to_user_str(request_tz_convert(comment.created, self.request)),
+                   'modified_user_str' : datetime_to_user_str(request_tz_convert(comment.modified, self.request)),
+#                   'created_str' : datetime_to_str(comment.created), # TODO change to a simple number as modified if possible
+                   'created' : datetime_to_epoch(comment.created), # TODO change to a simple number as modified if possible
+                   'modified' : datetime_to_epoch(comment.modified),  
+#                   'modified' : time.mktime(comment.modified.timetuple()),  
+#                   'created' : datetime_to_js_date_str(comment.created),
+                   'reply_to_id' : comment.reply_to_id,
+                   'replies' : replies,
+                   'name' : comment.get_name(), 
+                   'email' : comment.get_email(), 
+                   'logged_author' : (comment.user != None), 
+                   'title':comment.title,
+                   'content':comment.content, 
+                   'content_html':comment.content_html, 
+                   'tags': ', '.join(parse_tag_input(comment.tags)), 
+                   'format': comment.format, 
+                   'start_wrapper' : comment.start_wrapper, 
+                   'end_wrapper' : comment.end_wrapper,
+                   'start_offset' : comment.start_offset, 
+                   'end_offset' : comment.end_offset,
+                   'state' : comment.state,
+                   'permalink' : reverse('text-view-show-comment', args=[text.key, comment.key]),
+                   # permission
+                   'can_edit' : can_edit,
+                   'can_delete' : can_delete,
+                   'can_moderate' : can_moderate,
+                   }
+        if isinstance(obj, Tag) :
+            tag = obj
+            # RBE each time issuing a db request to find comments related to this tag !!! TODO  
+            return { 'ids' : [t.id for t in tag.items.all()], 'name' : tag.name, 'font_size' : tag.font_size}            
+
+        return simplejson.JSONEncoder.default(self, obj)
+
+def experiment() :
+    sleep(5) ;
+    return {"key":"value"}
+
+def read_comment_args(request):
+    name = request.POST.get('name', None)
+    email = request.POST.get('email', None)
+    if name != None :
+        name = name.lower().strip()
+    if email != None :
+        email = email.lower().strip()
+
+    title = request.POST['title'].strip()
+    content = request.POST['content'].strip() 
+    
+    tags = request.POST['tags']
+
+    reply_to_id = request.POST.get('reply_to_id', None)
+    
+    format = request.POST['format'] 
+
+    start_wrapper = request.POST.get('start_wrapper', None)
+    end_wrapper = request.POST.get('end_wrapper', None)
+    start_offset = request.POST.get('start_offset', None)
+    end_offset = request.POST.get('end_offset', None)
+    
+    if start_wrapper :
+        start_wrapper = int(start_wrapper.strip())
+    if end_wrapper :
+        end_wrapper = int(end_wrapper.strip())
+    if start_offset :
+        start_offset = int(start_offset.strip())
+    if end_offset :
+        end_offset = int(end_offset.strip())
+    
+    return name, email, title, content, tags, reply_to_id, format, start_wrapper, end_wrapper, start_offset, end_offset
+
+def validate_comment_args(name, email, title, content, tags):
+    errors = {}
+    if name != None : 
+        if name == "" :
+            errors['name'] = _(u'name is required')   
+    if email != None :
+        if email == "" :
+            errors['email'] = _(u'email is required')
+        if not is_valid_email(email) :
+            errors['email'] = _('invalid email')
+    if title == "" :
+        errors['title'] = _(u'title is required')   
+    if content == "" :
+        errors['content'] = _(u'content is required')
+        
+    tag_errors = validate_tags(tags)
+    if tag_errors != "" :
+        errors['tags'] = tag_errors
+        
+    return errors
+
+@has_perm_on_comment("can_delete_comment")
+def remove_comment(request, key, comment_key):
+    ret={}
+    try:
+        text = Text.objects.get(key=key)
+        comment = Comment.objects.get(key = comment_key)
+        comment.delete()
+        ret['msg'] = _(u'comment removed')
+        register_activity(request, "comment_removed", text=text, comment=comment)
+    except ObjectDoesNotExist: 
+        pass
+    return ret ;
+
+@has_perm_on_comment("can_edit_comment")
+def edit_comment(request, key, comment_key):
+    state = request.POST.get('state', None)
+    change_state = state and state in comment_states     
+    
+    errors = {}
+    if not change_state : # moderation action
+        change_scope = request.POST.get('change_scope', None)
+    
+        name, email, title, content, tags, reply_to_id, format, start_wrapper, end_wrapper, start_offset, end_offset = read_comment_args(request)
+    
+        errors = validate_comment_args(name, email, title, content, tags)
+         
+        if (change_scope) and start_wrapper=="" :
+            errors['selection_place'] = selection_place_error_msg   
+            
+        content_html = pandoc_convert(content, format, "html", full=False)
+
+    ret = {} 
+    if errors != {} :
+        ret['errors'] = errors
+    else :
+    # INSERT
+    # TODO check version still exist ...
+        #comment = Comment.objects.get(id=edit_comment_id)
+        comment = Comment.objects.get(key=comment_key)
+        if change_state : # moderation action
+            comment.state = state 
+        else :
+            comment.name = name
+            comment.email = email
+            comment.title = title
+            comment.content = content
+            comment.content_html = content_html
+            comment.tags = tags
+
+            if change_scope :
+                comment.start_wrapper = start_wrapper
+                comment.start_offset = start_offset
+                comment.end_wrapper = end_wrapper
+                comment.end_offset = end_offset
+            
+        comment.save()
+        
+        ret['comment'] = comment
+        ret['msg'] = _(u'comment saved')
+    return ret
+    
+@has_perm_on_text("can_create_comment")
+def add_comment(request, key):
+#    if edit_comment_id : #
+#    if self.request.user.is_anonymous() : # accessing via an admin url ?
+#    and comment.user == self.request.user
+    user = None if request.user.is_anonymous() else request.user 
+    name, email, title, content, tags, reply_to_id, format, start_wrapper, end_wrapper, start_offset, end_offset = read_comment_args(request)
+    errors = {} 
+    errors = validate_comment_args(name, email, title, content, tags)
+
+    if start_wrapper == "" :
+        errors['selection_place'] = selection_place_error_msg   
+
+    #TODO validate pandoc conversion
+    content_html = pandoc_convert(content, format, "html", full=False)
+        
+    ret = {} 
+    if errors != {} :
+        ret['errors'] = errors
+    else :
+    # INSERT
+    # TODO check version still exist ...
+        reply_to = None
+        if reply_to_id :
+            reply_to = Comment.objects.get(id=reply_to_id)
+            
+        text = Text.objects.get(key=key)
+        text_version = text.get_latest_version()
+        
+        comment_state = 'approved' if text_version.mod_posteriori else 'pending'
+        comment = Comment.objects.create(state=comment_state, text_version=text_version, user=user, name=name, email=email, title=title, content=content, content_html=content_html, tags = tags, start_wrapper = start_wrapper, end_wrapper = end_wrapper, start_offset = start_offset, end_offset = end_offset, reply_to=reply_to)
+        
+        if text_version.mod_posteriori or has_perm(request, 'can_view_unapproved_comment', text=text) : 
+            ret['comment'] = comment
+            ret['msg'] = _(u"comment saved")
+        else :
+            ret['msg'] = _(u"comment saved, it is being held for moderation")
+        
+        if AUTO_CONTRIB_REGISTER:
+            Notification.objects.set_notification_to_own_discussions(text=text, email_or_user=user or email)            
+        register_activity(request, "comment_created", text, comment)
+    return ret
+
+#we need to call comments_thread from here this function will be very expensive 
+# TODO: stupid get rid of text argument
+def get_filter_datas(request, text_version, text):
+    from django.db.models import Count
+    from datetime import datetime, timedelta
+
+    allowed_ids = [c.id for c in comments_thread(request, text_version, text)] 
+    allowed_comments = Comment.objects.filter(Q(text_version=text_version),Q(deleted=False),Q(id__in=allowed_ids)) 
+    #print allowed_ids 
+
+    # authors
+#    names = list(Comment.objects.filter(text_version__text__key=key).filter(user__isnull=True).values('name').annotate(nb_comments=Count('id'))) #.order_by('name'))
+    names = list(allowed_comments.filter(user__isnull=True).values('name').annotate(nb_comments=Count('id'))) #.order_by('name'))
+    names += list(User.objects.filter(Q(comment__text_version=text_version),Q(comment__deleted=False), Q(comment__id__in=allowed_ids)).extra(select={'name': "username"}).values('name').annotate(nb_comments=Count('id'))) #.order_by('username'))
+    names.sort(key = lambda obj:obj["name"])
+
+    # dates
+    # TODO maybe optimize by comparing dates in python and saving these 'by day db requests'
+    nb_days = [1, 3, 7, 30]
+    dates = []
+    today = datetime.today()
+    for nb_day in nb_days :
+        day_date = today - timedelta(nb_day)
+        dates.append({'nb_day' : nb_day, 'nb_day_date':datetime_to_epoch(day_date), 'nb_comments':allowed_comments.filter(modified__gt = day_date).count()})
+    
+    # tags
+    comment_ids = [c.id for c in allowed_comments]
+    tags = list(Tag.objects.filter(items__content_type = ContentType.objects.get_for_model(Comment),items__object_id__in=comment_ids).values("name").annotate(nb_comments=Count('id')).distinct().order_by('name'))
+
+    # states
+    states = []
+    for state in comment_states :
+        states.append({'state' : state, 'nb_comments':allowed_comments.filter(state = state).count()})
+    
+    return {'names':names, 'dates':dates, 'tags':tags, 'states':states}
+
+#def get_ordered_ids(text_version_id):
+#    comments_and_replies = Comment.objects.filter(text_version__id=text_version_id)
+#    comments = comments_and_replies.filter(reply_to__isnull=True)
+#    
+#    dic = {}
+#    for c in comments_and_replies :
+#        top_comment = c.top_comment()
+#        max_modif = dic.get(top_comment.id, c.modified)
+#        dic[top_comment.id] = c.modified if max_modif < c.modified else max_modif
+#    
+#    ordered_comment_ids = {'scope' : [c.id for c in comments.order_by('start_wrapper','start_offset','end_offset')],
+#                           'thread_modified' : map(operator.itemgetter(0), sorted(dic.items(), key=operator.itemgetter(1)))}
+#    return ordered_comment_ids
+   
+def validate_tags(tags):
+    if tags :
+        try :
+            if len(tags) > 250 : 
+                return _(u"Tags input must be no longer than 250 characters.")
+            TagField().formfield().clean(tags)
+        except ValidationError, e :
+            return ",".join(e.messages) 
+    return ''
+
+MAX_NB_TAGS_IN_COMMENT_CLOUD = 30
+def get_tagcloud(key) :
+    tagCloud = Tag.objects.cloud_for_model(Comment, steps=8, distribution=LOGARITHMIC, filters=dict(text_version__text__key=key))
+    return tagCloud
+    
+# returns a flat list of viewable comments and their replies ordered as they should be : 
+# order is : 
+# 'start_wrapper','start_offset','end_wrapper','end_offset' for 'real' comments
+# 'created' for replies
+# TODO: get rid of text here, keep text_version
+def comments_thread(request, text_version, text) : 
+    commentsnoreply = text_version.comment_set.filter(reply_to__isnull=True)#id=3)
+    viewable_commentsnoreply = get_viewable_comments(request, commentsnoreply, text, order_by = ('start_wrapper','start_offset','end_wrapper','end_offset'))
+    viewable_comments = []
+    for cc in viewable_commentsnoreply :
+        viewable_comments += list_viewable_comments(request, [cc], text)
+    return viewable_comments
+