src/cm/models.py
changeset 0 40c8f766c9b8
child 5 c3594e4df7c1
equal deleted inserted replaced
-1:000000000000 0:40c8f766c9b8
       
     1 from cm.converters.pandoc_converters import \
       
     2     CHOICES_INPUT_FORMATS as CHOICES_INPUT_FORMATS_PANDOC, \
       
     3     DEFAULT_INPUT_FORMAT as DEFAULT_INPUT_FORMAT_PANDOC, pandoc_convert
       
     4 from cm.models_base import PermanentModel, KeyManager, Manager, KeyModel, AuthorModel
       
     5 from cm.models_utils import *
       
     6 from cm.utils.dj import absolute_reverse
       
     7 from cm.utils.date import datetime_to_user_str
       
     8 from cm.utils.comment_positioning import compute_new_comment_positions
       
     9 from django import forms
       
    10 from django.db.models import Q
       
    11 from django.template.loader import render_to_string
       
    12 from django.conf import settings
       
    13 from django.template import RequestContext
       
    14 from django.contrib.auth.models import Permission
       
    15 from django.contrib.contenttypes import generic
       
    16 from django.contrib.contenttypes.models import ContentType
       
    17 from django.core.files.base import ContentFile
       
    18 from django.core.urlresolvers import reverse
       
    19 from django.template.defaultfilters import timesince
       
    20 from django.db import models
       
    21 from django.utils.translation import ugettext as _, ugettext_lazy, ugettext_noop
       
    22 from tagging.fields import TagField
       
    23 import pickle
       
    24 from django.db import connection
       
    25 
       
    26 
       
    27 
       
    28 class TextManager(Manager):
       
    29     def create_text(self, title, format, content, note, name, email, tags, user=None, state='approved', **kwargs):
       
    30         text = self.create(name=name, email=email, user=user, state=state)
       
    31         text_version = TextVersion.objects.create(title=title, format=format, content=content, text=text, note=note, name=name, email=email, tags=tags, user=user)
       
    32         return text
       
    33     
       
    34     def create_new_version(self, text, title, format, content, note, name, email, tags, user=None, **kwargs):
       
    35         text_version = TextVersion.objects.create(title=title, format=format, content=content, text=text, note=note, name=name, email=email, tags=tags, user=user)
       
    36         return text_version
       
    37     
       
    38 class Text(PermanentModel, AuthorModel):
       
    39     modified = models.DateTimeField(auto_now=True)
       
    40     created = models.DateTimeField(auto_now_add=True)
       
    41 
       
    42     private_feed_key = models.CharField(max_length=20, db_index=True, unique=True, blank=True, null=True, default=None)
       
    43 
       
    44     # denormalized fields
       
    45     last_text_version = models.ForeignKey("TextVersion", related_name='related_text', null=True, blank=True)
       
    46     title = models.TextField()
       
    47 
       
    48     objects = TextManager()
       
    49     
       
    50     def get_latest_version(self):
       
    51         return self.last_text_version
       
    52     
       
    53     def fetch_latest_version(self):
       
    54         versions = self.get_versions()
       
    55         if versions:
       
    56             return versions[0]
       
    57         else:
       
    58             return None
       
    59     
       
    60     def update_denorm_fields(self):
       
    61         real_last_text_version = self.fetch_latest_version()
       
    62     
       
    63         modif = False
       
    64         if real_last_text_version and real_last_text_version != self.last_text_version:
       
    65             self.last_text_version = real_last_text_version
       
    66             modif = True
       
    67             
       
    68         if real_last_text_version and real_last_text_version.title and real_last_text_version.title != self.title:
       
    69             self.title = real_last_text_version.title
       
    70             modif = True
       
    71         
       
    72         if real_last_text_version and real_last_text_version.modified != self.modified:
       
    73             self.modified = real_last_text_version.modified
       
    74             modif = True
       
    75             
       
    76         if modif:
       
    77             self.save()
       
    78 
       
    79                 
       
    80     def get_title(self):
       
    81         return self.get_latest_version().title
       
    82     
       
    83     def get_versions(self):
       
    84         """
       
    85         Versions with most recent first
       
    86         """
       
    87         versions = TextVersion.objects.filter(text__exact=self).order_by('-created')
       
    88         # TODO: use new postgresql 8.4 row_number as extra select to do that
       
    89         for index in xrange(len(versions)):
       
    90             v = versions[index]
       
    91             # version_number is 1-based
       
    92             setattr(v, 'version_number', len(versions) - index)
       
    93         #for v in versions:
       
    94         #    print v.created,v.id,v.version_number
       
    95         return versions
       
    96 
       
    97     def get_version(self, version_number):        
       
    98         """
       
    99         Get version number 'version_number' (1-based)
       
   100         """
       
   101         version = TextVersion.objects.filter(text__exact=self).order_by('created')[version_number - 1:version_number][0]
       
   102         return version
       
   103         
       
   104     def get_inversed_versions(self):
       
   105         versions = TextVersion.objects.filter(text__exact=self).order_by('created')
       
   106         # TODO: use new postgresql 8.4 row_number as extra select to do that
       
   107         for index in xrange(len(versions)):
       
   108             v = versions[index]
       
   109             # version_number is 1-based
       
   110             setattr(v, 'version_number', index + 1)
       
   111         return versions
       
   112 
       
   113     def get_versions_number(self):
       
   114         return self.get_versions().count()
       
   115 
       
   116     def is_admin(self, adminkey=None):
       
   117         if adminkey and self.adminkey == adminkey:
       
   118             return True
       
   119         else:
       
   120             return False
       
   121 
       
   122     def revert_to_version(self, v_id):
       
   123         text_version = self.get_version(int(v_id))
       
   124         new_text_version = TextVersion.objects.duplicate(text_version, True)
       
   125         return new_text_version
       
   126         
       
   127     def edit(self, new_title, new_format, new_content, new_tags=None, new_note=None, keep_comments=True, new_version=True):
       
   128         text_version = self.get_latest_version()
       
   129             
       
   130         if new_version:        
       
   131             text_version = TextVersion.objects.duplicate(text_version, keep_comments)
       
   132         text_version.edit(new_title, new_format, new_content, new_tags, new_note, keep_comments)        
       
   133         return text_version 
       
   134         
       
   135     def __unicode__(self):
       
   136         return self.title    
       
   137 
       
   138 DEFAULT_INPUT_FORMAT = getattr(settings, 'DEFAULT_INPUT_FORMAT', DEFAULT_INPUT_FORMAT_PANDOC)
       
   139 CHOICES_INPUT_FORMATS = getattr(settings, 'CHOICES_INPUT_FORMATS', CHOICES_INPUT_FORMATS_PANDOC)
       
   140 
       
   141 class TextVersionManager(models.Manager):
       
   142 
       
   143     def duplicate(self, text_version, duplicate_comments=True):
       
   144         #import pdb;pdb.set_trace()
       
   145         old_comment_set = set(text_version.comment_set.all())
       
   146         text_version.id = None
       
   147         #import pdb;pdb.set_trace()
       
   148         text_version.save()
       
   149         
       
   150         duplicate_text_version = text_version
       
   151         
       
   152         if duplicate_comments:
       
   153             old_comment_map = {}
       
   154             while len(old_comment_set):
       
   155                 for c in old_comment_set:
       
   156                     if not c.reply_to or c.reply_to.id in old_comment_map:
       
   157                         old_id = c.id
       
   158                         old_comment_set.remove(c)
       
   159                         reply_to = None
       
   160                         if c.reply_to:                            
       
   161                             reply_to = old_comment_map[c.reply_to.id]  
       
   162                         c2 = Comment.objects.duplicate(c, duplicate_text_version, reply_to)
       
   163                         old_comment_map[old_id] = c2
       
   164                         break
       
   165                  
       
   166         return duplicate_text_version
       
   167         
       
   168 class TextVersion(AuthorModel):
       
   169     modified = models.DateTimeField(auto_now=True)
       
   170     created = models.DateTimeField(auto_now_add=True)
       
   171 
       
   172     title = models.TextField(ugettext_lazy("Title"))
       
   173     format = models.CharField(ugettext_lazy("Format"), max_length=20, blank=False, default=DEFAULT_INPUT_FORMAT, choices=CHOICES_INPUT_FORMATS)
       
   174     content = models.TextField(ugettext_lazy("Content"))
       
   175     tags = TagField(ugettext_lazy("Tags"), max_length=1000)
       
   176 
       
   177     note = models.CharField(ugettext_lazy("Note"), max_length=100, null=True, blank=True)
       
   178 
       
   179     mod_posteriori = models.BooleanField(ugettext_lazy('Moderation a posteriori?'), default=True)
       
   180 
       
   181     text = models.ForeignKey("Text")
       
   182 
       
   183     objects = TextVersionManager()
       
   184     
       
   185     def get_content(self, format='html'):
       
   186         converted_content = pandoc_convert(self.content, self.format, format)
       
   187         return converted_content 
       
   188 
       
   189 #    def _get_comments(self, user = None, filter_reply = 0):        
       
   190 #        """
       
   191 #        get comments viewable by this user (user = None or user = AnonymousUser => everyone)
       
   192 #        filter_reply = 0: comments and replies
       
   193 #                       1: comments
       
   194 #                       2: replies
       
   195 #        """        
       
   196 #        from cm.security import has_perm_on_text # should stay here to avoid circular dependencies
       
   197 #        
       
   198 #        if has_perm(user, 'can_view_unapproved_comment', self.text):
       
   199 #            comments = self.comment_set.all()
       
   200 #        elif has_perm(user, 'can_view_approved_comment', self.text):
       
   201 #            comments = self.comment_set.filter(visible=True)
       
   202 #        elif has_perm(user, 'can_view_own_comment', self.text):
       
   203 #            comments = self.comment_set.filter(user=user)
       
   204 #        else:
       
   205 #            return Comment.objects.none() # empty queryset
       
   206 #        if filter_reply:
       
   207 #            comments = comments.filter)
       
   208 #        return comments
       
   209 #
       
   210 #    def get_comments_as_json(self, user = None):
       
   211 #        return simplejson.dumps(self._get_comments(user, filter_reply=0))
       
   212 #
       
   213 #    def get_comments_and_replies(self, user = None):
       
   214 #        return (self.get_comments(user),
       
   215 #                self.get_replies(user))
       
   216 #
       
   217     def get_comments(self):
       
   218         "Warning: data access without security"
       
   219         return self.comment_set.filter(reply_to=None, deleted=False)
       
   220 
       
   221     def get_replies(self):
       
   222         "Warning: data access without security"
       
   223         return self.comment_set.filter(~Q(reply_to == None), Q(deleted=False))
       
   224     
       
   225     def __unicode__(self):
       
   226         return '<%d> %s' % (self.id, self.title)    
       
   227 
       
   228     def edit(self, new_title, new_format, new_content, new_tags=None, new_note=None, keep_comments=True): # TODO : tags
       
   229         if not keep_comments :
       
   230             self.comment_set.all().delete()
       
   231         elif self.content != new_content or new_format != self.format:
       
   232             comments = self.get_comments() ;
       
   233             tomodify_comments, toremove_comments = compute_new_comment_positions(self.content, self.format, new_content, new_format, comments)
       
   234             #print "tomodify_comments",len(tomodify_comments)
       
   235             #print "toremove_comments",len(toremove_comments)
       
   236             [comment.save() for comment in tomodify_comments]
       
   237             [comment.delete() for comment in toremove_comments]
       
   238         self.title = new_title
       
   239         if new_tags:
       
   240             self.tags = new_tags
       
   241         if new_note:
       
   242             self.note = new_note
       
   243         self.content = new_content
       
   244         self.format = new_format
       
   245         self.save()
       
   246         
       
   247 class CommentManager(Manager):
       
   248     
       
   249     def duplicate(self, comment, text_version, reply_to=None):
       
   250         comment.id = None
       
   251         comment.text_version = text_version
       
   252         if reply_to:
       
   253             comment.reply_to = reply_to
       
   254         self.update_keys(comment)
       
   255         comment.save()
       
   256         return comment
       
   257     
       
   258 class Comment(PermanentModel, AuthorModel):
       
   259     modified = models.DateTimeField(auto_now=True)
       
   260     created = models.DateTimeField(auto_now_add=True)
       
   261 
       
   262     text_version = models.ForeignKey("TextVersion")
       
   263 
       
   264     # comment_set will be replies
       
   265     reply_to = models.ForeignKey("Comment", null=True, blank=True)
       
   266 
       
   267     title = models.TextField()
       
   268     content = models.TextField()
       
   269     content_html = models.TextField()
       
   270     
       
   271     format = models.CharField(_("Format:"), max_length=20, blank=False, default=DEFAULT_INPUT_FORMAT, choices=CHOICES_INPUT_FORMATS)
       
   272 
       
   273     tags = TagField()
       
   274         
       
   275     start_wrapper = models.IntegerField(null=True, blank=True)
       
   276     end_wrapper = models.IntegerField(null=True, blank=True)
       
   277     start_offset = models.IntegerField(null=True, blank=True)
       
   278     end_offset = models.IntegerField(null=True, blank=True)
       
   279 
       
   280     objects = CommentManager()
       
   281     
       
   282     def __unicode__(self):
       
   283         return '<%d> %s' % (self.id, self.title)    
       
   284         
       
   285     def is_reply(self):
       
   286         return self.reply_to != None
       
   287     
       
   288     def is_thread_full_visible(self):
       
   289         cur_comment = self
       
   290         if not cur_comment.state == 'approved':
       
   291             return False
       
   292         
       
   293         while cur_comment.reply_to != None:
       
   294             cur_comment = cur_comment.reply_to
       
   295             if not cur_comment.state == 'approved':
       
   296                 return False
       
   297             
       
   298         return True
       
   299     
       
   300     def top_comment(self):
       
   301         if self.reply_to == None :
       
   302             return self
       
   303         else : 
       
   304             return self.reply_to.top_comment()
       
   305     
       
   306     def depth(self):
       
   307         if self.reply_to == None :
       
   308             return 0
       
   309         else : 
       
   310             return 1 + self.reply_to.depth()
       
   311     
       
   312     def delete(self):
       
   313         PermanentModel.delete(self)
       
   314         # delete replies
       
   315         [c.delete() for c in self.comment_set.all()]
       
   316     
       
   317 # http://docs.djangoproject.com/en/dev/topics/files/#topics-files
       
   318 
       
   319 # default conf values
       
   320 DEFAULT_CONF = {
       
   321                 'workspace_name' : 'Workspace',
       
   322                 'site_url' : settings.SITE_URL,
       
   323                 'email_from' : settings.DEFAULT_FROM_EMAIL,
       
   324                 }
       
   325 
       
   326 from cm.role_models import change_role_model
       
   327 
       
   328 class ConfigurationManager(models.Manager):
       
   329     def set_workspace_name(self, workspace_name):
       
   330         if workspace_name and not self.get_key('workspace_name')!=u'Workspace':
       
   331             self.set_key('workspace_name', _(u"%(workspace_name)s's workspace") %{'workspace_name':workspace_name})
       
   332 
       
   333     def get_key(self, key, default_value=None):
       
   334         try:
       
   335             return self.get(key=key).value
       
   336         except Configuration.DoesNotExist:
       
   337             return DEFAULT_CONF.get(key, default_value)
       
   338         
       
   339     def set_key(self, key, value):
       
   340         conf, created = self.get_or_create(key=key)
       
   341         if created or conf.value != value:
       
   342             conf.value = value
       
   343             conf.save()
       
   344             if key == 'workspace_role_model':
       
   345                 change_role_model(value)
       
   346 
       
   347     def __getitem__(self, key):
       
   348         return self.get_key(key, None)
       
   349     
       
   350 import base64
       
   351 
       
   352 class Configuration(models.Model):
       
   353     key = models.TextField(blank=False) # , unique=True cannot be added: creates error on mysql (?)
       
   354     raw_value = models.TextField(blank=False)
       
   355     
       
   356     def get_value(self):
       
   357         return pickle.loads(base64.b64decode(self.raw_value.encode('utf8')))
       
   358         
       
   359     def set_value(self, value):        
       
   360         self.raw_value = base64.b64encode(pickle.dumps(value, 0)).encode('utf8')
       
   361                 
       
   362     value = property(get_value, set_value)
       
   363                 
       
   364     objects = ConfigurationManager()
       
   365     
       
   366     def __unicode__(self):
       
   367         return '%s: %s' % (self.key, self.value)    
       
   368     
       
   369 ApplicationConfiguration = Configuration.objects     
       
   370 
       
   371 class AttachmentManager(KeyManager):
       
   372     def create_attachment(self, text_version, filename, data):
       
   373         attach = self.create(text_version=text_version)
       
   374         ff = ContentFile(data)
       
   375         attach.data.save(filename, ff)
       
   376         return attach
       
   377     
       
   378 class Attachment(KeyModel):
       
   379     data = models.FileField(upload_to="attachments/%Y/%m/%d/", max_length=1000)
       
   380     text_version = models.ForeignKey(TextVersion)
       
   381 
       
   382     objects = AttachmentManager()
       
   383     
       
   384 class NotificationManager(KeyManager):
       
   385     def create_notification(self, text, type, email_or_user):
       
   386         prev_notification = self.get_notification_to_own_discussions(text, type, email_or_user)
       
   387         if not prev_notification:
       
   388             notification = self.create(text=text, type=type)
       
   389             notification.set_email_or_user(email_or_user)
       
   390             return notification
       
   391         else:
       
   392             return prev_notification 
       
   393 
       
   394     def get_notification_to_own_discussions(self, text, type, email_or_user):
       
   395         if isinstance(email_or_user,unicode):
       
   396             prev_notifications = Notification.objects.filter(text=text, type=type, email=email_or_user)
       
   397         else:
       
   398             prev_notifications = Notification.objects.filter(text=text, type=type, user=email_or_user)
       
   399         if prev_notifications:
       
   400             return prev_notifications[0]
       
   401         else:
       
   402             return None
       
   403      
       
   404     def set_notification_to_own_discussions(self, text, email_or_user, active=True):
       
   405         if active:
       
   406             notification = self.create_notification(text, 'own', email_or_user)
       
   407             if not notification.active:
       
   408                 notification.active = True
       
   409                 notification.save()                
       
   410         else:
       
   411             notification = self.create_notification(text, 'own', email_or_user)
       
   412             notification.active = False
       
   413             notification.save()                
       
   414     
       
   415     def subscribe_to_own_text(self, text, user):
       
   416         return self.create_notification(text, None, user)
       
   417     
       
   418 class Notification(KeyModel, AuthorModel):
       
   419     text = models.ForeignKey(Text, null=True, blank=True)
       
   420     type = models.CharField(max_length=30, null=True, blank=True)
       
   421     active = models.BooleanField(default=True) # active = False means user desactivation
       
   422     
       
   423     objects = NotificationManager()
       
   424     
       
   425     def desactivate_notification_url(self):
       
   426         return reverse('desactivate-notification', args=[self.adminkey])
       
   427 
       
   428     def desactivate(self):    
       
   429         if self.type=='own':
       
   430             self.active = False
       
   431             self.save()
       
   432         else:
       
   433             self.delete()
       
   434     
       
   435 # right management
       
   436 class UserRoleManager(models.Manager):
       
   437     def create_userroles_text(self, text):
       
   438         # make sure every user has a userrole on this text
       
   439         for user in User.objects.all():
       
   440             userrole, _ = self.get_or_create(user=user, text=text)
       
   441         # anon user
       
   442         userrole, _ = self.get_or_create(user=None, text=text)
       
   443         # anon global user
       
   444         global_userrole, _ = self.get_or_create(user=None, text=None)
       
   445             
       
   446 class UserRole(models.Model):
       
   447     role = models.ForeignKey("Role", null=True, blank=True)
       
   448     
       
   449     # user == null => anyone
       
   450     user = models.ForeignKey(User, null=True, blank=True)
       
   451     
       
   452     # text == null => any text (workspace role)
       
   453     text = models.ForeignKey(Text, null=True, blank=True)
       
   454     
       
   455     objects = UserRoleManager()
       
   456     
       
   457     class Meta:
       
   458         unique_together = (('role', 'user', 'text',))
       
   459 
       
   460     def __unicode__(self):
       
   461         if self.role:
       
   462             rolename = _(self.role.name)
       
   463         else:
       
   464             rolename = ''
       
   465             
       
   466         if self.user:
       
   467             return u"%s: %s %s %s" % (self.__class__.__name__, self.user.username, self.text, rolename)
       
   468         else:
       
   469             return u"%s: *ALL* %s %s" % (self.__class__.__name__, self.text, rolename)
       
   470     
       
   471     def __repr__(self):
       
   472         return self.__unicode__()
       
   473 
       
   474 from cm.models_base import generate_key
       
   475 from cm.utils.misc import update
       
   476 
       
   477 class Role(models.Model):
       
   478     """
       
   479     'Static' application roles 
       
   480     """
       
   481     name = models.CharField(ugettext_lazy('name'), max_length=50, unique=True)
       
   482     description = models.TextField(ugettext_lazy('description'))
       
   483     #order = models.IntegerField(unique=True)
       
   484     permissions = models.ManyToManyField(Permission, related_name="roles")
       
   485 
       
   486     global_scope = models.BooleanField('global scope', default=False) # applies to global scope only
       
   487     anon = models.BooleanField('anonymous', default=False) # role possible for anonymous users?
       
   488     
       
   489     def __unicode__(self):
       
   490         return _(self.name)
       
   491     
       
   492     def __hash__(self):
       
   493         return self.id
       
   494 
       
   495     def name_i18n(self):
       
   496         return _(self.name)
       
   497     
       
   498 from django.utils.safestring import mark_safe
       
   499  
       
   500 class RegistrationManager(KeyManager):
       
   501     def activate_user(self, activation_key):
       
   502         """
       
   503         Validates an activation key and activates the corresponding
       
   504         ``User`` if valid.
       
   505         If the key is valid , returns the ``User`` as second arg
       
   506         First is boolean indicating if user has just been activated
       
   507         """
       
   508         # Make sure the key we're trying conforms to the pattern of a
       
   509         # SHA1 hash; if it doesn't, no point trying to look it up in
       
   510         # the database.
       
   511         try:
       
   512             profile = self.get(admin_key=activation_key)
       
   513         except self.model.DoesNotExist:
       
   514             return False, False
       
   515         user = profile.user
       
   516         activated = False
       
   517         if not user.is_active:
       
   518             user.is_active = True
       
   519             user.save()
       
   520             activated = True
       
   521         return (activated, user)
       
   522 
       
   523     def _create_manager(self, email, username, password, first_name, last_name):
       
   524         if username and email and password and len(User.objects.filter(username=username)) == 0:
       
   525             user = User.objects.create(username=username, email=email, first_name=first_name, last_name=last_name, is_active=True)
       
   526             user.set_password(password)
       
   527             user.save()
       
   528             
       
   529             profile = UserProfile.objects.create(user=user)
       
   530                     
       
   531             manager = Role.objects.get(name='Manager')
       
   532             UserRole.objects.create(text=None, user=user, role=manager)
       
   533             return user
       
   534         else:
       
   535             return None
       
   536     
       
   537         
       
   538     def create_inactive_user(self, email, send_invitation, **kwargs):
       
   539         #prevent concurrent access 
       
   540         cursor = connection.cursor()
       
   541         sql = "LOCK TABLE auth_user IN EXCLUSIVE MODE"
       
   542         cursor.execute(sql)
       
   543         
       
   544         try:
       
   545             user_with_email = User.objects.get(email__iexact=email)
       
   546         except User.DoesNotExist:
       
   547             user = User.objects.create(username=email, email=email)
       
   548             profile = UserProfile.objects.create(user=user)
       
   549             update(user, kwargs)
       
   550             update(profile, kwargs)
       
   551             
       
   552             user.is_active = False
       
   553             user.save()
       
   554             profile.save()
       
   555             
       
   556             note = kwargs.get('note', None) 
       
   557             if send_invitation:
       
   558                 profile.send_activation_email(note)
       
   559             return user
       
   560         else:
       
   561             return user_with_email
       
   562         
       
   563 
       
   564 from cm.utils.mail import send_mail
       
   565 
       
   566 class UserProfile(KeyModel):
       
   567     modified = models.DateTimeField(auto_now=True)
       
   568     created = models.DateTimeField(auto_now_add=True)
       
   569     
       
   570     user = models.ForeignKey(User, unique=True)
       
   571 
       
   572     allow_contact = models.BooleanField(ugettext_lazy(u'Allow contact'), default=True, help_text=ugettext_lazy(u"Allow email messages from other users"))    
       
   573     preferred_language = models.CharField(ugettext_lazy(u'Preferred language'), max_length=2, default="en")
       
   574     is_temp = models.BooleanField(default=False)
       
   575     is_email_error = models.BooleanField(default=False)
       
   576     is_suspended = models.BooleanField(ugettext_lazy(u'Suspended access'), default=False) # used to disable access or to wait for approval when registering
       
   577 
       
   578     objects = RegistrationManager()
       
   579 
       
   580     class Meta:
       
   581         permissions = (
       
   582             ("can_create_user", "Can create user"),
       
   583             ("can_delete_user", "Can delete user"),
       
   584         )
       
   585         
       
   586     def __unicode__(self):
       
   587         return unicode(self.user)
       
   588 
       
   589     def global_userrole(self):
       
   590         try:
       
   591             return UserRole.objects.get(user=self.user, text=None)
       
   592         except UserRole.DoesNotExist:
       
   593             return None
       
   594 
       
   595     def global_userrole(self):
       
   596         try:
       
   597             return UserRole.objects.get(user=self.user, text=None)
       
   598         except UserRole.DoesNotExist:
       
   599             return None
       
   600 
       
   601     def admin_print(self):
       
   602         if self.is_suspended:
       
   603             if self.user.is_active:
       
   604                 return mark_safe('%s (%s)' % (self.user.username, _(u'suspended'),))
       
   605             else:
       
   606                 return mark_safe('%s (%s)' % (self.user.username, _(u'waiting approval'),))
       
   607         else:
       
   608             if self.user.is_active:
       
   609                 return mark_safe('%s' % self.user.username) 
       
   610             else:
       
   611                 email_username = self.user.email.split('@')[0]
       
   612                 return mark_safe('%s (%s)' % (self.user.username, _(u'pending'),))
       
   613 
       
   614     def simple_print(self):
       
   615         if self.user.is_active:
       
   616             return self.user.username 
       
   617         else:
       
   618             return self.user.email
       
   619 
       
   620     def send_activation_email(self, note=None):
       
   621         self._send_act_invit_email(note=note, template='email/activation_email.txt')
       
   622 
       
   623     def send_invitation_email(self, note=None):
       
   624         self._send_act_invit_email(note=note, template='email/invitation_email.txt')
       
   625         
       
   626     def _send_act_invit_email(self, template, note=None):
       
   627         subject = _(u'Invitation')
       
   628     
       
   629         activate_url = reverse('user-activate', args=[self.adminkey])
       
   630         message = render_to_string(template,
       
   631                                    { 
       
   632                                      'activate_url' : activate_url,
       
   633                                      'note' : note,
       
   634                                      'CONF': ApplicationConfiguration
       
   635                                       })
       
   636     
       
   637         send_mail(subject, message, ApplicationConfiguration['email_from'], [self.user.email])
       
   638         
       
   639 from django.db.models import signals
       
   640 
       
   641 #def create_profile(sender, **kwargs):
       
   642 #    created = kwargs['created']
       
   643 #    if created:
       
   644 #        user = kwargs['instance']
       
   645 #        UserProfile.objects.create(user = user)
       
   646 
       
   647 def delete_profile(sender, **kwargs):
       
   648     user_profile = kwargs['instance']
       
   649     user = user_profile.user
       
   650     user.delete()
       
   651     
       
   652 #signals.post_save.connect(create_profile, sender=User)
       
   653 signals.post_delete.connect(delete_profile, sender=UserProfile)
       
   654 
       
   655 class ActivityManager(models.Manager):
       
   656     pass
       
   657 
       
   658 class Activity(models.Model):
       
   659     created = models.DateTimeField(auto_now_add=True)
       
   660     originator_user = models.ForeignKey(User, related_name='originator_activity', null=True, blank=True, default=None)
       
   661     text = models.ForeignKey(Text, null=True, blank=True, default=None)
       
   662     text_version = models.ForeignKey(TextVersion, null=True, blank=True, default=None)
       
   663     comment = models.ForeignKey(Comment, null=True, blank=True, default=None)
       
   664     user = models.ForeignKey(User, null=True, blank=True, default=None)
       
   665     type = models.CharField(max_length=30)
       
   666     ip = models.IPAddressField(null=True, blank=True, default=None)
       
   667     
       
   668     objects = ActivityManager()
       
   669     
       
   670     # viewable activities (i.e. now 'text-view')
       
   671     VIEWABLE_ACTIVITIES = {
       
   672                    'view_comments' : ['comment_created', 'comment_removed'],
       
   673                    'view_users' : ['user_created', 'user_activated', 'user_refused', 'user_enabled', 'user_approved', 'user_suspended'],
       
   674                    'view_texts' : ['text_created', 'text_removed', 'text_edited', 'text_edited_new_version'],
       
   675                    }
       
   676     ACTIVITIES_TYPES = reduce(list.__add__, VIEWABLE_ACTIVITIES.values())
       
   677     
       
   678     IMGS = {
       
   679             'text_created' : u'page_add_small.png',
       
   680             'text_removed' : u'page_delete_small.png',
       
   681             'text_edited'  : u'page_save_small.png',
       
   682             'text_edited_new_version' : u'page_save_small.png',
       
   683             'user_created' : u'user_add_small.png',
       
   684             'user_enabled' : u'user_add_small.png',
       
   685             'user_refused': u'user_delete_small.png',
       
   686             'user_suspended': u'user_delete_small.png',
       
   687             'user_approved': u'user_add_small.png',
       
   688             'user_activated' : u'user_go.png',
       
   689             'comment_created' : u'note_add_small.png',
       
   690             'comment_removed' : u'note_delete_small.png',
       
   691         }
       
   692     
       
   693     #type/msg
       
   694     MSGS = {
       
   695          'text_edited' : _(u'Text %(link_to_text)s edited'),
       
   696          'text_edited_new_version' : _(u'Text %(link_to_text)s edited (new version created)'),
       
   697          'text_created' :  _(u'Text %(link_to_text)s added'),
       
   698          'text_removed' : _(u'Text %(link_to_text)s removed'),
       
   699          'comment_created' : _(u'Comment %(link_to_comment)s added on text %(link_to_text)s'),
       
   700          'comment_removed' : _(u'Comment %(link_to_comment)s removed from text %(link_to_text)s'),
       
   701          'user_created' : _(u'User %(username)s added'),
       
   702          'user_enabled' : _(u'User %(username)s access to workspace enabled'),
       
   703          'user_refused' : _(u'User %(username)s access to workspace refused'),
       
   704          'user_suspended' : _(u'User %(username)s access to workspace suspended'),
       
   705          'user_activated' : _(u'User %(username)s access to workspace activated'),
       
   706          'user_approved' : _(u'User %(username)s has activated his account'),
       
   707          }
       
   708     
       
   709     def is_same_user(self, other_activity):
       
   710         if (self.originator_user != None or other_activity.originator_user != None) and self.originator_user != other_activity.originator_user:
       
   711             return False
       
   712         else:
       
   713             return self.ip != None and self.ip == other_activity.ip
       
   714 
       
   715     def linkable_text_title(self, html=True, link=True):
       
   716         # html: whether or not output sould be html
       
   717         format_args = {'link':absolute_reverse('text-view', args=[self.text.key]), 'title':self.text.title}
       
   718         if html and not self.text.deleted :
       
   719             return mark_safe(u'<a href="%(link)s">%(title)s</a>' % format_args)
       
   720         else:
       
   721             if link and not self.text.deleted:
       
   722                 return u'%(title)s (%(link)s)' % format_args
       
   723             else:             
       
   724                 return self.text.title ;
       
   725 
       
   726     def linkable_comment_title(self, html=True, link=True):
       
   727         if self.comment:
       
   728             format_args = {'link':absolute_reverse('text-view-show-comment', args=[self.text.key, self.comment.key]), 'title':self.comment.title}
       
   729             if html and not self.comment.deleted and not self.text.deleted:
       
   730                 return mark_safe(u'<a href="%(link)s">%(title)s</a>' % format_args)
       
   731             else :
       
   732                 if link and not self.comment.deleted and not self.text.deleted:
       
   733                     return u'%(title)s (%(link)s)' % format_args
       
   734                 else:
       
   735                     return self.comment.title ;
       
   736         else:
       
   737             return u''
       
   738 
       
   739     def __unicode__(self):
       
   740         return u"%s %s %s %s %s" % (self.type, self.originator_user, self.text, self.comment, self.user)
       
   741     
       
   742     def img_name(self):
       
   743         return self.IMGS.get(self.type)
       
   744 
       
   745     def printable_data_nohtml_link(self):
       
   746         return self.printable_data(html=False, link=True)
       
   747         
       
   748     def printable_data(self, html=True, link=True):
       
   749         msg = self.MSGS.get(self.type, None)
       
   750         if msg:
       
   751             return mark_safe(msg % {
       
   752                                      'link_to_text' : self.linkable_text_title(html=html, link=link) if self.text else None,
       
   753                                      'link_to_comment' : self.linkable_comment_title(html=html, link=link) if self.comment else None,
       
   754                                      'username' : self.user.username if self.user else None,
       
   755                                     })
       
   756         return ''
       
   757     
       
   758     def printable_metadata(self, html=True):
       
   759         ret = []
       
   760         if self.type == 'user_activated':
       
   761             ret.append(_(u'by "%(username)s"') % {'username' : self.originator_user.username})
       
   762             ret.append(' ')
       
   763         ret.append(_(u"%(time_since)s ago") % {'time_since':timesince(self.created)})
       
   764         return ''.join(ret)
       
   765 
       
   766     def printable_metadata_absolute(self, html=True):
       
   767         ret = []
       
   768         if self.type == 'user_activated':
       
   769             ret.append(_(u'by "%(username)s"') % {'username' : self.originator_user.username})
       
   770             ret.append(u' ')
       
   771         ret.append(datetime_to_user_str(self.created))
       
   772         return u''.join(ret)
       
   773 
       
   774 import cm.denorm_engine
       
   775 import cm.admin
       
   776 import cm.main
       
   777 import cm.activity
       
   778 import cm.notifications
       
   779 
       
   780 # we fill username with email so we need a bigger value 
       
   781 User._meta.get_field('username').max_length = 75