src/cm/client.py
changeset 0 40c8f766c9b8
child 12 f69ff46d3240
equal deleted inserted replaced
-1:000000000000 0:40c8f766c9b8
       
     1 from django.forms.fields import email_re
       
     2 from django.http import HttpResponse, HttpResponseRedirect
       
     3 from django.core.exceptions import ObjectDoesNotExist
       
     4 from django.forms import ValidationError
       
     5 from django.utils import simplejson
       
     6 from django.utils.translation import ugettext as _
       
     7 from tagging.models import Tag
       
     8 from tagging.utils import parse_tag_input, LOGARITHMIC, calculate_cloud
       
     9 from tagging.models import TaggedItem
       
    10 from cm.models import *
       
    11 from cm.utils.timezone import request_tz_convert
       
    12 from itertools import groupby
       
    13 from time import mktime, sleep
       
    14 from cm.converters.pandoc_converters import pandoc_convert
       
    15 from cm.security import get_viewable_comments, list_viewable_comments, has_perm, has_perm_on_text, has_perm_on_comment, has_own_perm
       
    16 from cm.activity import register_activity
       
    17 from cm.utils.date import datetime_to_user_str, datetime_to_epoch
       
    18 from cm.cm_settings import AUTO_CONTRIB_REGISTER
       
    19 from settings import CLIENT_DATE_FMT
       
    20 import re
       
    21 import time
       
    22 import operator
       
    23 
       
    24 
       
    25 selection_place_error_msg = _(u'A selection is required. Select in the text the part your comment applies to.')
       
    26 comment_states = ('approved', 'unapproved', 'pending')
       
    27 
       
    28 def is_valid_email(email):
       
    29     if email_re.match(email) : 
       
    30         return True 
       
    31     return False
       
    32 
       
    33 #
       
    34 def jsonize(obj, request):
       
    35     return simplejson.dumps(obj, cls=RequestComplexEncoder, request=request)
       
    36 
       
    37 class RequestComplexEncoder(simplejson.JSONEncoder):
       
    38     def __init__(self, request, **kw):
       
    39         self.request = request
       
    40         simplejson.JSONEncoder.__init__(self, **kw)
       
    41         
       
    42         
       
    43     def default(self, obj):
       
    44         if isinstance(obj, Comment) :
       
    45             comment = obj
       
    46             #replies = list(comment.comment_set.order_by('created'))
       
    47             text=comment.text_version.text
       
    48             replies = get_viewable_comments(self.request, comment.comment_set.all(), text)
       
    49             
       
    50             # can_view == true because of get_viewable_comments filter
       
    51             can_moderate = has_perm(self.request, 'can_edit_comment', text)   
       
    52             can_edit = has_perm(self.request, 'can_edit_comment', text) or has_own_perm(self.request, 'can_edit_comment_own', text, comment)  
       
    53             can_delete = has_perm(self.request, 'can_delete_comment', text) or has_own_perm(self.request, 'can_delete_comment_own', text, comment)
       
    54             
       
    55             return {'id' : comment.id, 
       
    56                     'key' : comment.key,
       
    57                    'created_user_str' : datetime_to_user_str(request_tz_convert(comment.created, self.request)),
       
    58                    'modified_user_str' : datetime_to_user_str(request_tz_convert(comment.modified, self.request)),
       
    59 #                   'created_str' : datetime_to_str(comment.created), # TODO change to a simple number as modified if possible
       
    60                    'created' : datetime_to_epoch(comment.created), # TODO change to a simple number as modified if possible
       
    61                    'modified' : datetime_to_epoch(comment.modified),  
       
    62 #                   'modified' : time.mktime(comment.modified.timetuple()),  
       
    63 #                   'created' : datetime_to_js_date_str(comment.created),
       
    64                    'reply_to_id' : comment.reply_to_id,
       
    65                    'replies' : replies,
       
    66                    'name' : comment.get_name(), 
       
    67                    'email' : comment.get_email(), 
       
    68                    'logged_author' : (comment.user != None), 
       
    69                    'title':comment.title,
       
    70                    'content':comment.content, 
       
    71                    'content_html':comment.content_html, 
       
    72                    'tags': ', '.join(parse_tag_input(comment.tags)), 
       
    73                    'format': comment.format, 
       
    74                    'start_wrapper' : comment.start_wrapper, 
       
    75                    'end_wrapper' : comment.end_wrapper,
       
    76                    'start_offset' : comment.start_offset, 
       
    77                    'end_offset' : comment.end_offset,
       
    78                    'state' : comment.state,
       
    79                    'permalink' : reverse('text-view-show-comment', args=[text.key, comment.key]),
       
    80                    # permission
       
    81                    'can_edit' : can_edit,
       
    82                    'can_delete' : can_delete,
       
    83                    'can_moderate' : can_moderate,
       
    84                    }
       
    85         if isinstance(obj, Tag) :
       
    86             tag = obj
       
    87             # RBE each time issuing a db request to find comments related to this tag !!! TODO  
       
    88             return { 'ids' : [t.id for t in tag.items.all()], 'name' : tag.name, 'font_size' : tag.font_size}            
       
    89 
       
    90         return simplejson.JSONEncoder.default(self, obj)
       
    91 
       
    92 def experiment() :
       
    93     sleep(5) ;
       
    94     return {"key":"value"}
       
    95 
       
    96 def read_comment_args(request):
       
    97     name = request.POST.get('name', None)
       
    98     email = request.POST.get('email', None)
       
    99     if name != None :
       
   100         name = name.lower().strip()
       
   101     if email != None :
       
   102         email = email.lower().strip()
       
   103 
       
   104     title = request.POST['title'].strip()
       
   105     content = request.POST['content'].strip() 
       
   106     
       
   107     tags = request.POST['tags']
       
   108 
       
   109     reply_to_id = request.POST.get('reply_to_id', None)
       
   110     
       
   111     format = request.POST['format'] 
       
   112 
       
   113     start_wrapper = request.POST.get('start_wrapper', None)
       
   114     end_wrapper = request.POST.get('end_wrapper', None)
       
   115     start_offset = request.POST.get('start_offset', None)
       
   116     end_offset = request.POST.get('end_offset', None)
       
   117     
       
   118     if start_wrapper :
       
   119         start_wrapper = int(start_wrapper.strip())
       
   120     if end_wrapper :
       
   121         end_wrapper = int(end_wrapper.strip())
       
   122     if start_offset :
       
   123         start_offset = int(start_offset.strip())
       
   124     if end_offset :
       
   125         end_offset = int(end_offset.strip())
       
   126     
       
   127     return name, email, title, content, tags, reply_to_id, format, start_wrapper, end_wrapper, start_offset, end_offset
       
   128 
       
   129 def validate_comment_args(name, email, title, content, tags):
       
   130     errors = {}
       
   131     if name != None : 
       
   132         if name == "" :
       
   133             errors['name'] = _(u'name is required')   
       
   134     if email != None :
       
   135         if email == "" :
       
   136             errors['email'] = _(u'email is required')
       
   137         if not is_valid_email(email) :
       
   138             errors['email'] = _('invalid email')
       
   139     if title == "" :
       
   140         errors['title'] = _(u'title is required')   
       
   141     if content == "" :
       
   142         errors['content'] = _(u'content is required')
       
   143         
       
   144     tag_errors = validate_tags(tags)
       
   145     if tag_errors != "" :
       
   146         errors['tags'] = tag_errors
       
   147         
       
   148     return errors
       
   149 
       
   150 @has_perm_on_comment("can_delete_comment")
       
   151 def remove_comment(request, key, comment_key):
       
   152     ret={}
       
   153     try:
       
   154         text = Text.objects.get(key=key)
       
   155         comment = Comment.objects.get(key = comment_key)
       
   156         comment.delete()
       
   157         ret['msg'] = _(u'comment removed')
       
   158         register_activity(request, "comment_removed", text=text, comment=comment)
       
   159     except ObjectDoesNotExist: 
       
   160         pass
       
   161     return ret ;
       
   162 
       
   163 @has_perm_on_comment("can_edit_comment")
       
   164 def edit_comment(request, key, comment_key):
       
   165     state = request.POST.get('state', None)
       
   166     change_state = state and state in comment_states     
       
   167     
       
   168     errors = {}
       
   169     if not change_state : # moderation action
       
   170         change_scope = request.POST.get('change_scope', None)
       
   171     
       
   172         name, email, title, content, tags, reply_to_id, format, start_wrapper, end_wrapper, start_offset, end_offset = read_comment_args(request)
       
   173     
       
   174         errors = validate_comment_args(name, email, title, content, tags)
       
   175          
       
   176         if (change_scope) and start_wrapper=="" :
       
   177             errors['selection_place'] = selection_place_error_msg   
       
   178             
       
   179         content_html = pandoc_convert(content, format, "html", full=False)
       
   180 
       
   181     ret = {} 
       
   182     if errors != {} :
       
   183         ret['errors'] = errors
       
   184     else :
       
   185     # INSERT
       
   186     # TODO check version still exist ...
       
   187         #comment = Comment.objects.get(id=edit_comment_id)
       
   188         comment = Comment.objects.get(key=comment_key)
       
   189         if change_state : # moderation action
       
   190             comment.state = state 
       
   191         else :
       
   192             comment.name = name
       
   193             comment.email = email
       
   194             comment.title = title
       
   195             comment.content = content
       
   196             comment.content_html = content_html
       
   197             comment.tags = tags
       
   198 
       
   199             if change_scope :
       
   200                 comment.start_wrapper = start_wrapper
       
   201                 comment.start_offset = start_offset
       
   202                 comment.end_wrapper = end_wrapper
       
   203                 comment.end_offset = end_offset
       
   204             
       
   205         comment.save()
       
   206         
       
   207         ret['comment'] = comment
       
   208         ret['msg'] = _(u'comment saved')
       
   209     return ret
       
   210     
       
   211 @has_perm_on_text("can_create_comment")
       
   212 def add_comment(request, key):
       
   213 #    if edit_comment_id : #
       
   214 #    if self.request.user.is_anonymous() : # accessing via an admin url ?
       
   215 #    and comment.user == self.request.user
       
   216     user = None if request.user.is_anonymous() else request.user 
       
   217     name, email, title, content, tags, reply_to_id, format, start_wrapper, end_wrapper, start_offset, end_offset = read_comment_args(request)
       
   218     errors = {} 
       
   219     errors = validate_comment_args(name, email, title, content, tags)
       
   220 
       
   221     if start_wrapper == "" :
       
   222         errors['selection_place'] = selection_place_error_msg   
       
   223 
       
   224     #TODO validate pandoc conversion
       
   225     content_html = pandoc_convert(content, format, "html", full=False)
       
   226         
       
   227     ret = {} 
       
   228     if errors != {} :
       
   229         ret['errors'] = errors
       
   230     else :
       
   231     # INSERT
       
   232     # TODO check version still exist ...
       
   233         reply_to = None
       
   234         if reply_to_id :
       
   235             reply_to = Comment.objects.get(id=reply_to_id)
       
   236             
       
   237         text = Text.objects.get(key=key)
       
   238         text_version = text.get_latest_version()
       
   239         
       
   240         comment_state = 'approved' if text_version.mod_posteriori else 'pending'
       
   241         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)
       
   242         
       
   243         if text_version.mod_posteriori or has_perm(request, 'can_view_unapproved_comment', text=text) : 
       
   244             ret['comment'] = comment
       
   245             ret['msg'] = _(u"comment saved")
       
   246         else :
       
   247             ret['msg'] = _(u"comment saved, it is being held for moderation")
       
   248         
       
   249         if AUTO_CONTRIB_REGISTER:
       
   250             Notification.objects.set_notification_to_own_discussions(text=text, email_or_user=user or email)            
       
   251         register_activity(request, "comment_created", text, comment)
       
   252     return ret
       
   253 
       
   254 #we need to call comments_thread from here this function will be very expensive 
       
   255 # TODO: stupid get rid of text argument
       
   256 def get_filter_datas(request, text_version, text):
       
   257     from django.db.models import Count
       
   258     from datetime import datetime, timedelta
       
   259 
       
   260     allowed_ids = [c.id for c in comments_thread(request, text_version, text)] 
       
   261     allowed_comments = Comment.objects.filter(Q(text_version=text_version),Q(deleted=False),Q(id__in=allowed_ids)) 
       
   262     #print allowed_ids 
       
   263 
       
   264     # authors
       
   265 #    names = list(Comment.objects.filter(text_version__text__key=key).filter(user__isnull=True).values('name').annotate(nb_comments=Count('id'))) #.order_by('name'))
       
   266     names = list(allowed_comments.filter(user__isnull=True).values('name').annotate(nb_comments=Count('id'))) #.order_by('name'))
       
   267     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'))
       
   268     names.sort(key = lambda obj:obj["name"])
       
   269 
       
   270     # dates
       
   271     # TODO maybe optimize by comparing dates in python and saving these 'by day db requests'
       
   272     nb_days = [1, 3, 7, 30]
       
   273     dates = []
       
   274     today = datetime.today()
       
   275     for nb_day in nb_days :
       
   276         day_date = today - timedelta(nb_day)
       
   277         dates.append({'nb_day' : nb_day, 'nb_day_date':datetime_to_epoch(day_date), 'nb_comments':allowed_comments.filter(modified__gt = day_date).count()})
       
   278     
       
   279     # tags
       
   280     comment_ids = [c.id for c in allowed_comments]
       
   281     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'))
       
   282 
       
   283     # states
       
   284     states = []
       
   285     for state in comment_states :
       
   286         states.append({'state' : state, 'nb_comments':allowed_comments.filter(state = state).count()})
       
   287     
       
   288     return {'names':names, 'dates':dates, 'tags':tags, 'states':states}
       
   289 
       
   290 #def get_ordered_ids(text_version_id):
       
   291 #    comments_and_replies = Comment.objects.filter(text_version__id=text_version_id)
       
   292 #    comments = comments_and_replies.filter(reply_to__isnull=True)
       
   293 #    
       
   294 #    dic = {}
       
   295 #    for c in comments_and_replies :
       
   296 #        top_comment = c.top_comment()
       
   297 #        max_modif = dic.get(top_comment.id, c.modified)
       
   298 #        dic[top_comment.id] = c.modified if max_modif < c.modified else max_modif
       
   299 #    
       
   300 #    ordered_comment_ids = {'scope' : [c.id for c in comments.order_by('start_wrapper','start_offset','end_offset')],
       
   301 #                           'thread_modified' : map(operator.itemgetter(0), sorted(dic.items(), key=operator.itemgetter(1)))}
       
   302 #    return ordered_comment_ids
       
   303    
       
   304 def validate_tags(tags):
       
   305     if tags :
       
   306         try :
       
   307             if len(tags) > 250 : 
       
   308                 return _(u"Tags input must be no longer than 250 characters.")
       
   309             TagField().formfield().clean(tags)
       
   310         except ValidationError, e :
       
   311             return ",".join(e.messages) 
       
   312     return ''
       
   313 
       
   314 MAX_NB_TAGS_IN_COMMENT_CLOUD = 30
       
   315 def get_tagcloud(key) :
       
   316     tagCloud = Tag.objects.cloud_for_model(Comment, steps=8, distribution=LOGARITHMIC, filters=dict(text_version__text__key=key))
       
   317     return tagCloud
       
   318     
       
   319 # returns a flat list of viewable comments and their replies ordered as they should be : 
       
   320 # order is : 
       
   321 # 'start_wrapper','start_offset','end_wrapper','end_offset' for 'real' comments
       
   322 # 'created' for replies
       
   323 # TODO: get rid of text here, keep text_version
       
   324 def comments_thread(request, text_version, text) : 
       
   325     commentsnoreply = text_version.comment_set.filter(reply_to__isnull=True)#id=3)
       
   326     viewable_commentsnoreply = get_viewable_comments(request, commentsnoreply, text, order_by = ('start_wrapper','start_offset','end_wrapper','end_offset'))
       
   327     viewable_comments = []
       
   328     for cc in viewable_commentsnoreply :
       
   329         viewable_comments += list_viewable_comments(request, [cc], text)
       
   330     return viewable_comments
       
   331