Název projektu: Sphene Community Tools

Domovská stránka projektu: http://sct.sphene.net/wiki/show/Start/

Zadání

Do existujiciho projektu Sphene Community Tools, coz je komunitni portal s wiki, forem a blogem, postaveny na frameworku Django, pridam pocitani karmy. Tj, moznost hodnotit jednotlive prispevky ve foru a uzivatelum pocitat jejich „kvalitu“.

Kazdy prispevek ve foru dostane 2 tlacitka pro libi/nelibi. Bude se pocitat hodnoceni jednotlivych prispevku a rovnez hodnoceni uzivatelu. Hodnoceni uzivatelu bude zhodnocovat:

  • Dobu po kterou jsou zaregistrovani/neaktivni
  • Pocet prispevku
  • Hodnoceni vlastnich prispevku

Hodnoceni nesmi prilis favorizovat nektere skupiny uzivatelu (jiz dlouho registrovane…)

Výsledné řešení

Výsledné řešení splňuje požadavky, tj: máme karmu, hodnoceni libi/nelibi, a hodnocení zahrnující výše zmíněná kriteria. Karma se dává v rozsahu (-4,4) a k tomu jsou mapovány úrovně červ, krysa, kráva, člověk, moudrý člověk a bůh.

Rovnice pro výpočet je následující

Rovnice

(arctan vypadá asi takhle :-),

alt text

čímž je docíleno, že k nejhorším/nejlepším karmám se člověk blíží mnohem pomaleji, než kolem karmy neutrální.

Výsledek pak vypadá asi takto

alt text

Zhodnocení

Komunikace s hlavním (a téměř jediným) vývojářem SCT byla velice vstřícná a bylo znát jeho nadšení o můj zájem. Avšak vzhledem k tomu, že nemá žádné jasně dané a formalizované procesy, tak nemůžu tušit, kdy se můj diff objeví ve vývojové větvi a zda-li krom prostého „díky“ se jím bude vlastně zabývat.

Prezentace

Ohloh

Ohloh profile for Jakub Beleščák

Přílohy

Moje komunikace:

Subject:
Re: sct cooperation
From:
Herbert Poul <herbert@poul.at>
Date:
Fri, 12 Mar 2010 10:03:48 +0200
To:
Jakub Beles(c(ák <jakub.belescak@centrum.cz>

Hi Jakub,

the diff looks ok, i will merge it when i have some more time

thanks,
  herbert

> > On Thu, Mar 11, 2010 at 12:44 PM, Jakub Beles(c(ák
> > <jakub.belescak@centrum.cz> wrote:
> > gr8,
> > well the formula is still being tested and is discussed a lot, but it
> > always
> > depends on the concrete community , its size etc...
> >
> > I was thinking of making it optional, but that would require for the
> > settings
> > values to be accessible in templates, through which is the karma and
> > its controls displayed.
> > It would be really gr8, to take karma to account to the heat calculation,
> > but I can not decypher, how this "call function by its name stored in
> > settings"
> > is done.
> >
> > Have a nice weekend
> >
> > Jakub
> >
> > Herbert Poul wrote:
>> >> Hi Jakub,
>> >>
>> >> i am very glad to hear that you want to contribute part of your work
>> >> back. If you like we can further discuss the features (although i
>> >> guess since you know more about your requirements and have thought
>> >> more deeply about it i can't necessary comment much about your
>> >> suggested formular  :)  but if you need help for how to integrate it
>> >> into SCT code i might be able to give a hint.. )
>> >> for me the best way would be if you could send me a diff (ie. patch)
>> >> to 0.6 - if you like i could even give you write permission to the SVN
>> >> repository so you can develop this in a SCT branch (if you don't use
>> >> your own SVN already)..
>> >>
>> >> to your idea: this sounds very good - for the periodic calculation:
>> >> there is already a maintenance signal
>> >> (sphene.community.signals.maintenance) which is used for the 'heat'
>> >> calculation of forum threads - maybe it would make sense to use the
>> >> same? (it is triggered with the 'sph_maintenance' management command)
>> >>
>> >> i'm looking forward to your changes  ;)  do you think it is possible to
>> >> make this feature optional (ie. so it can be turned off using a
>> >> SPH_SETTINGS option)?
>> >>
>> >>
>> >> thanks,
>> >>   herbert
>> >>
>> >> On Mon, Feb 22, 2010 at 12:04 PM, Jakub Beles(c(ák
>> >> <jakub.belescak@centrum.cz> wrote:
>>> >>> Hello there,
>>> >>> We've decided to use SCT as community portal for one small faculty.
>>> >>> What
>>> >>> they wanted, that wasn't in SCT is karma. I would like to
>>> >>> participate in
>>> >>> SCT development. So, what I can do now is:
>>> >>> 1) Discuss what is the best way to implement it.
>>> >>> 2) Send you changed version 0.6
>>> >>> 3) Send you diff from 0.6
>>> >>> 4) Forget about it, because it is completely useless
>>> >>>
>>> >>> Which one would you like?  :-) 
>>> >>>
>>> >>>
>>> >>> My idea, and what I've partly implemented is:
>>> >>> 1) posts have +,-. 1 user - 1 post - 1 vote
>>> >>> 2) users have karma, which is
>>> >>>    a) precounted in database
>>> >>>    b) calculated with a function, which can be user overiden
>>> >>>    c) displayed with a template tag in interval -4,4
>>> >>>    d) precounted periodically by management/command
>>> >>>
>>> >>> so far, my colleauge suggested this  calculation function
>>> >>>  karma = 4 * 2/pi * arctan k*[ 0.03*(today-registered) + 0.01*num_posts
>>> >>> - 0.03*(today-last_post) + sum{karma_post*(2*karma_author + 1)} ]
>>> >>> the coefficients (4, 0.03, 0.01 and 0.03) will be put in SPH_SETTINGS
>>> >>>
>>> >>> Jakub Belescak
>>> >>>
>>> >>> PS:  We are also considering integrating user2user messaging, perhaps
>>> >>> instant user2user messaging. http://code.google.com/p/django-messages/
>>> >>> http://code.google.com/p/django-messaging/
>>> >>>
>>> >>>
>>> >>>     
>> >>
>> >>   
> >
> >

Diff oproti verzi 0.6

Index: communitytools/sphenecoll/sphene/community/templatetags/sph_karma.py
===================================================================
--- communitytools/sphenecoll/sphene/community/templatetags/sph_karma.py    (.../home/beda/prace/pythonws/socioportal/communitytools)   (working copy)
+++ communitytools/sphenecoll/sphene/community/templatetags/sph_karma.py    (.../https://svn.toh.cz/socioportal/trunk/communitytools)   (revision 3)
@@ -1,58 +0,0 @@
-from sphene.community.models import CommunityUserProfile
-from django import template
-register = template.Library()
-
-from sphene.sphboard.models import Post, PostKarmers 
-from django.contrib.auth.models import User
-from django.conf import settings
-import math
-import datetime
-
-def calculate_karma_logic(community_user_profile):
-    karma_linearity_koeficient      = 0.1 #if not hasattr(settings, 'SPH_SETTINGS') or not 'karma_linearity_koeficient' in settings.SPH_SETTINGS else settings.SPH_SETTINGS['karma_linearity_koeficient'] 
-    karma_age_koeficient            = 0.02 #if not hasattr(settings, 'SPH_SETTINGS') or not 'karma_linearity_koeficient' in settings.SPH_SETTINGS else settings.SPH_SETTINGS['karma_age_koeficient']
-    karma_post_count_koeficient     = 0.05 #if not hasattr(settings, 'SPH_SETTINGS') or not 'karma_linearity_koeficient' in settings.SPH_SETTINGS else settings.SPH_SETTINGS['karma_post_count_koeficient']
-    karma_inactivity_koeficient     = 0.005 #if not hasattr(settings, 'SPH_SETTINGS') or not 'karma_linearity_koeficient' in settings.SPH_SETTINGS else settings.SPH_SETTINGS['karma_inactivity_koeficient']
-    us = community_user_profile
-    
-    age = (datetime.datetime.today() - us.user.date_joined).days
-    posts = Post.objects.filter(author=us.user,is_hidden=0).order_by('-postdate')
-    post_count = len(posts)
-    if post_count >0:
-        last_post = posts[0].postdate
-    else:
-        last_post = us.user.date_joined
-    inactivity = (datetime.datetime.today()-last_post).days
-    sumicka = 0
-    for post1 in posts:
-        for pok in PostKarmers.objects.filter(post = post1):
-            if pok.like:
-                sumicka+= 2*pok.user.communityuserprofile_set.all()[0].karma+1 
-            else:
-                sumicka-= 2*pok.user.communityuserprofile_set.all()[0].karma+1
-    return 2/math.pi * math.atan( karma_linearity_koeficient * ( karma_age_koeficient*age + karma_post_count_koeficient*post_count - karma_inactivity_koeficient*inactivity + sumicka ) )
-
-def get_karma(community_user_profile):
-    if community_user_profile.karma_counted_date != community_user_profile.karma_counted_date.today( ):
-        community_user_profile.karma_counted_date = community_user_profile.karma_counted_date.today( )
-        community_user_profile.karma = calculate_karma_logic(community_user_profile)
-        community_user_profile.save()
-    return community_user_profile.karma
-
-@register.simple_tag
-def sph_user_karma(userv):
-#    return userv.id
-    try:
-        usr = CommunityUserProfile.objects.get( user__id = userv.id )
-#        usr = userv.communityuserprofile_set.all()[0]
-    except CommunityUserProfile.DoesNotExist:
-        usr = CommunityUserProfile(user = userv)
-        usr.save()
-    try:
-        karma = round(4*get_karma(usr),2)
-    except :
-        karma = round(4*get_karma(usr),2)
-    if karma<=0:
-        return str(karma)
-    else:
-        return "+"+str(karma)



Index: communitytools/sphenecoll/sphene/community/models.py
===================================================================
--- communitytools/sphenecoll/sphene/community/models.py    (.../home/beda/prace/pythonws/socioportal/communitytools)   (working copy)
+++ communitytools/sphenecoll/sphene/community/models.py    (.../https://svn.toh.cz/socioportal/trunk/communitytools)   (revision 3)
@@ -144,13 +144,6 @@
     avatar_height = models.IntegerField(blank = True, null = True, )
     avatar_width = models.IntegerField(blank = True, null = True, )

-    karma = models.FloatField(default = 0, null = False, editable = False)
-    karma_counted_date = models.DateField(auto_now_add=True, editable = False)
-    def karma_counted(self):
-#        if self.karma_counted_date != self.karma_counted_date.today( ):
-#            self.karma = calculate_karma_logic(self)
-#            self.save() 
-        return self.karma

     changelog = ( ( '2007-08-10 00', 'alter', 'ADD avatar varchar(100)'   ),
                   ( '2007-08-10 01', 'alter', 'ADD avatar_height integer' ),
@@ -158,11 +151,6 @@
                   ( '2008-04-10 00', 'alter', 'ADD displayname varchar(250)' ),
                   ( '2008-04-10 01', 'update', "SET displayname = ''" ),
                   ( '2008-04-10 02', 'alter', 'ALTER displayname SET NOT NULL' ),
-                  ( '2009-01-16 00', 'alter', 'ADD karma integer NOT NULL', ),
-                  ( '2009-01-16 01', 'alter', 'ALTER COLUMN karma SET DEFAULT 0', ),
-                  ( '2009-02-22 00', 'alter', 'ALTER COLUMN karma TYPE float', ),
-                  ( '2009-02-22 01', 'update', 'SET karma = 0', ),
-                  ( '2009-02-24 00', 'alter', 'ADD karma_counted_date date NOT NULL DEFAULT NOW()', ),
                 )

     class Meta:

Index: communitytools/sphenecoll/sphene/sphboard/views.py
===================================================================
--- communitytools/sphenecoll/sphene/sphboard/views.py  (.../home/beda/prace/pythonws/socioportal/communitytools)   (working copy)
+++ communitytools/sphenecoll/sphene/sphboard/views.py  (.../https://svn.toh.cz/socioportal/trunk/communitytools)   (revision 3)
@@ -10,14 +10,13 @@
 from sphene.community import PermissionDenied
 from sphene.community.middleware import get_current_sphdata
 from sphene.community.sphutils import sph_reverse, get_user_displayname, format_date, get_sph_setting, add_rss_feed, sph_render_to_response
-from sphene.community.models import CommunityUserProfile

 from sphene.generic import advanced_object_list as objlist

 from sphene.sphboard.forms import PollForm, PollChoiceForm, PostForm, \
                                   PostPollForm, PostAttachmentForm, \
                                   AnnotateForm, MoveAndAnnotateForm, MovePostForm
-from sphene.sphboard.models import Category, Post, PostAnnotation, ThreadInformation, Poll, PollChoice, PollVoters, POST_MARKUP_CHOICES, THREAD_TYPE_MOVED, THREAD_TYPE_DEFAULT, get_all_viewable_categories, ThreadLastVisit, CategoryLastVisit, PostKarmers
+from sphene.sphboard.models import Category, Post, PostAnnotation, ThreadInformation, Poll, PollChoice, PollVoters, POST_MARKUP_CHOICES, THREAD_TYPE_MOVED, THREAD_TYPE_DEFAULT, get_all_viewable_categories, ThreadLastVisit, CategoryLastVisit


 def showCategory(request, group, category_id = None, showType = None, slug = None):
@@ -842,12 +841,3 @@

     return str

-def karmize(request, post_id, like):
-    like = False if like=='0' else True
-    postobj = Post.objects.get(id=post_id)
-    if postobj._allow_karma():
-        postobj.karma_touch(like)
-        pk = PostKarmers(post = postobj, user = request.user, like = like)
-        pk.save()
-    return HttpResponseRedirect( request.GET['next'] )
-    
\ No newline at end of file

Index: communitytools/sphenecoll/sphene/sphboard/models.py
===================================================================
--- communitytools/sphenecoll/sphene/sphboard/models.py (.../home/beda/prace/pythonws/socioportal/communitytools)   (working copy)
+++ communitytools/sphenecoll/sphene/sphboard/models.py (.../https://svn.toh.cz/socioportal/trunk/communitytools)   (revision 3)
@@ -2,7 +2,6 @@
 from django.db import models
 from django.db.models import Q
 from django.db.models import signals
-from django.db.models import Count
 from django.core.urlresolvers import reverse
 from django.core.mail import send_mass_mail
 from django.core.cache import cache
@@ -566,42 +565,7 @@
     #  a custom category type might change this behavior tough by adding a 
     #  administration interface for hidden posts.)
     is_hidden = models.IntegerField(default = 0, editable = False, db_index = True )
-    
-    karma = models.IntegerField(default = 0, null = False, editable = False)
-    #like increases karma, not like decreases
-    def karma_touch(self, like):
-        if like:
-            self.karma = self.karma+1
-        else:
-            self.karma = self.karma-1
-        self.save()
-    #whether the active user has already voted karma
-    def _allow_karma(self, user = None):
-        if user == None: user = get_current_user()
-        if not user or not user.is_authenticated() or user==self.author:
-            return False
-        else:
-            try:
-                pk = PostKarmers.objects.get(post = self, user = user)
-                return False
-            except PostKarmers.DoesNotExist:
-                return True
-    allow_karma = property(fget=_allow_karma)
-    #how has the current user voted
-    def _my_karma(self, user = None):
-        if user == None: user = get_current_user()
-        try:
-            pk = PostKarmers.objects.get(post = self, user = user)
-            return pk.like
-        except PostKarmers.DoesNotExist:
-            return None
-    my_karma = property(fget=_my_karma)
-    def _positive_karma(self):
-        return PostKarmers.objects.filter(post = self, like = True).aggregate(Count('id'))['id__count']
-    positive_karma = property(fget=_positive_karma)
-    def _negative_karma(self):
-        return PostKarmers.objects.filter(post = self, like = False).aggregate(Count('id'))['id__count']
-    negative_karma = property(fget=_negative_karma)
+
     # allobjects also contain hidden posts.
     allobjects = models.Manager()
     # objects only contains non-hidden posts.
@@ -612,8 +576,6 @@
                   ( '2008-01-06 00', 'alter', 'ADD is_hidden INTEGER', ),
                   ( '2008-01-06 01', 'update', 'SET is_hidden = 0', ),
                   ( '2008-01-06 02', 'alter', 'ALTER is_hidden SET NOT NULL', ),
-                  ( '2009-01-16 00', 'alter', 'ADD karma integer NOT NULL', ),
-                  ( '2009-01-16 01', 'alter', 'ALTER COLUMN karma SET DEFAULT 0', ),
                   )

     def is_sticky(self):
@@ -740,7 +702,7 @@
         if not user or not user.is_authenticated():
             return False

-        if user.is_staff \
+        if user.is_superuser \
                or has_permission_flag( user, 'sphboard_hideallposts', self.category ):
             return True

@@ -1470,12 +1432,6 @@
         verbose_name = ugettext_lazy('Poll voter')
         verbose_name_plural = ugettext_lazy('Poll voters')

-class PostKarmers(models.Model):
-    post = models.ForeignKey( Post, editable = False)
-    user = models.ForeignKey( User, editable = False)
-    like = models.BooleanField( editable = False)
-    class Meta:
-        unique_together = (( 'post', 'user' ),)

 class BoardUserProfile(models.Model):
     user = models.ForeignKey( User, unique = True)

Index: communitytools/sphenecoll/sphene/sphboard/views.py
===================================================================
--- communitytools/sphenecoll/sphene/sphboard/views.py  (.../home/beda/prace/pythonws/socioportal/communitytools)   (working copy)
+++ communitytools/sphenecoll/sphene/sphboard/views.py  (.../https://svn.toh.cz/socioportal/trunk/communitytools)   (revision 3)
@@ -10,14 +10,13 @@
 from sphene.community import PermissionDenied
 from sphene.community.middleware import get_current_sphdata
 from sphene.community.sphutils import sph_reverse, get_user_displayname, format_date, get_sph_setting, add_rss_feed, sph_render_to_response
-from sphene.community.models import CommunityUserProfile

 from sphene.generic import advanced_object_list as objlist

 from sphene.sphboard.forms import PollForm, PollChoiceForm, PostForm, \
                                   PostPollForm, PostAttachmentForm, \
                                   AnnotateForm, MoveAndAnnotateForm, MovePostForm
-from sphene.sphboard.models import Category, Post, PostAnnotation, ThreadInformation, Poll, PollChoice, PollVoters, POST_MARKUP_CHOICES, THREAD_TYPE_MOVED, THREAD_TYPE_DEFAULT, get_all_viewable_categories, ThreadLastVisit, CategoryLastVisit, PostKarmers
+from sphene.sphboard.models import Category, Post, PostAnnotation, ThreadInformation, Poll, PollChoice, PollVoters, POST_MARKUP_CHOICES, THREAD_TYPE_MOVED, THREAD_TYPE_DEFAULT, get_all_viewable_categories, ThreadLastVisit, CategoryLastVisit


 def showCategory(request, group, category_id = None, showType = None, slug = None):
@@ -842,12 +841,3 @@

     return str

-def karmize(request, post_id, like):
-    like = False if like=='0' else True
-    postobj = Post.objects.get(id=post_id)
-    if postobj._allow_karma():
-        postobj.karma_touch(like)
-        pk = PostKarmers(post = postobj, user = request.user, like = like)
-        pk.save()
-    return HttpResponseRedirect( request.GET['next'] )
-    
\ No newline at end of file