root/lib/sct/sphenecoll/sphene/community/models.py

Revision 169:45a1e0bafadd, 23.2 kB (checked in by Daniele Varrazzo <piro@develer.com>, 1 year ago)

imported patch cache-group.patch

Line 
1 from django.db import models
2 from django.db.models import signals
3 from django.core.cache import cache
4 from django.contrib import admin
5 from django.contrib.auth.models import User
6 from django.contrib.contenttypes.models import ContentType
7 from django.contrib.contenttypes import generic
8 from sphene.community.sphpermalink import sphpermalink
9 from django.utils.translation import ugettext as _, ugettext_lazy
10 from django.db import connection
11 import logging
12 import re
13 logger = logging.getLogger('sphene.community.models')
14 # Create your models here.
15
16 class Group(models.Model):
17     name = models.CharField(max_length = 250)
18     longname = models.CharField(max_length = 250)
19     default_theme = models.ForeignKey('Theme', null = True, blank = True)
20     parent = models.ForeignKey('Group', null = True, blank = True)
21     baseurl = models.CharField(max_length = 250, help_text = 'The base URL under which this group will be available. Example: sct.sphene.net')
22
23     def get_name(self):
24         return self.longname or self.name
25
26     def recursiveName(self):
27         recname = ''
28         if self.parent:
29             recname = self.parent.recursiveName() + ' / '
30         return recname + self.name
31
32     def get_member(self, user):
33         try:
34             return GroupMember.objects.get( group = self,
35                                             user = user, )
36         except GroupMember.DoesNotExist:
37             return None
38
39     def get_baseurl(self):
40         """
41         Returns the "base URL" including http:// and without tailing slash.
42         """
43         url = self.baseurl
44         if not (url.startswith('http://') or url.startswith('https://')):
45             url = 'http://%s' % url
46         if url.endswith('/'):
47             url = url[0:-1]
48         return url
49
50     def __unicode__(self):
51         return self.name;
52
53 def get_group(name):
54     """Return a `Group` by name.
55
56     Cache the group and return the cached version if available.
57     Raise `Group.DoesNotExist` if not found.
58     """
59     group = cache.get(_get_group_cache_key(name))
60     if group is not None:
61         return group
62
63     # raise if not found
64     group = Group.objects.get( name__exact = name )
65     cache.add(_get_group_cache_key(name), group)
66
67     return group
68
69 def invalidate_group_cache(sender, instance, **kwargs):
70     """Signal handler to remove a `Group` from the cache."""
71     if instance.name:
72         cache.delete(_get_group_cache_key(instance.name))
73
74 signals.pre_save.connect(
75     invalidate_group_cache,
76     sender=Group)
77
78 def _get_group_cache_key(name):
79     return "__group__" + name
80    
81 USERLEVEL_CHOICES = (
82     (0, ugettext_lazy('Normal User')),
83     (5, ugettext_lazy('Administrator')),
84     )
85
86 class GroupMember(models.Model):
87         group = models.ForeignKey( Group )
88         user = models.ForeignKey( User, )
89         userlevel = models.IntegerField( choices = USERLEVEL_CHOICES )
90
91
92         changelog = ( ( '2008-04-06 00', 'alter', 'ADD userlevel integer', ),
93                       ( '2008-04-06 01', 'update', 'SET userlevel = 0', ),
94                       ( '2008-04-06 02', 'alter', 'ALTER userlevel SET NOT NULL', ), )
95
96
97         def get_userlevel_str(self):
98             for value, str in USERLEVEL_CHOICES:
99                 if value == self.userlevel:
100                     return str;
101
102
103
104 class Theme(models.Model):
105         name = models.CharField(max_length = 250)
106         path = models.CharField(max_length = 250)
107
108         def __unicode__(self):
109                 return self.name;
110
111
112 NAVIGATION_URL_TYPES = (
113         (0, 'Relative (e.g. /wiki/show/Start)'),
114         (1, 'Absolute (e.g. http://sphene.net')
115         )
116
117 NAVIGATION_TYPES = (
118         (0, 'Left Main Navigation'),
119         (1, 'Top navigation')
120         )
121
122 class Navigation(models.Model):
123         group = models.ForeignKey(Group)
124         label = models.CharField(max_length = 250)
125         href  = models.CharField(max_length = 250)
126         urltype = models.IntegerField( default = 0, choices = NAVIGATION_URL_TYPES )
127         sortorder = models.IntegerField( default = 100 )
128         navigationType = models.IntegerField( default = 0, choices = NAVIGATION_TYPES )
129
130
131         def __unicode__(self):
132                 return self.label
133
134         class Meta:
135                 ordering = ['sortorder']
136        
137
138 class ApplicationChangelog(models.Model):
139         app_label = models.CharField(max_length = 250)
140         model = models.CharField(max_length = 250)
141         version = models.CharField(max_length = 250)
142         applied = models.DateTimeField()
143
144         class Meta:
145                 get_latest_by = 'applied'
146
147
148 from sphene.community.sphsettings import get_sph_setting
149 #from sphene.community import sphutils
150
151 class CommunityUserProfile(models.Model):
152     user = models.ForeignKey( User, unique = True)
153     displayname = models.CharField(max_length = 250)
154     public_emailaddress = models.CharField(max_length = 250)
155    
156     avatar = models.ImageField( height_field = 'avatar_height',
157                                 width_field = 'avatar_width',
158                                 upload_to = get_sph_setting('community_avatar_upload_to'),
159                                 blank = True, null = True, )
160     avatar_height = models.IntegerField(blank = True, null = True, )
161     avatar_width = models.IntegerField(blank = True, null = True, )
162
163
164     changelog = ( ( '2007-08-10 00', 'alter', 'ADD avatar varchar(100)'   ),
165                   ( '2007-08-10 01', 'alter', 'ADD avatar_height integer' ),
166                   ( '2007-08-10 02', 'alter', 'ADD avatar_width integer'  ),
167                   ( '2008-04-10 00', 'alter', 'ADD displayname varchar(250)' ),
168                   ( '2008-04-10 01', 'update', "SET displayname = ''" ),
169                   ( '2008-04-10 02', 'alter', 'ALTER displayname SET NOT NULL' ),
170                 )
171
172 class CommunityUserProfileField(models.Model):
173     """ User profile fields, configurable through the django admin
174     interface. """
175     name = models.CharField(max_length = 250)
176     help_text = models.CharField(max_length = 250, blank = True, help_text = 'An optional help text displayed to the user.' )
177     regex = models.CharField(max_length = 250, blank = True, help_text = 'An optional regular expression to validate user input.', )
178     renderstring = models.CharField(max_length = 250, blank = True, help_text = 'An optional render string how the value should be displayed in the profile (e.g. &lt;a href="%(value)s"&gt;%(value)s&lt;/a&gt; - default: %(value)s' )
179     sortorder = models.IntegerField()
180
181     class Meta:
182         ordering = [ 'sortorder' ]
183
184
185 class CommunityUserProfileFieldValue(models.Model):
186     user_profile = models.ForeignKey( CommunityUserProfile )
187     profile_field = models.ForeignKey( CommunityUserProfileField )
188
189     value = models.CharField( max_length = 250 )
190
191     class Meta:
192         unique_together = (("user_profile", "profile_field"),)
193
194 class GroupTemplate(models.Model):
195     """
196     Represents a group specific template which can be used to overload
197     any django template from the filesystem.
198     """
199     group = models.ForeignKey(Group)
200     template_name = models.CharField( max_length = 250, db_index = True )
201     source = models.TextField()
202
203     def __unicode__(self):
204         return self.template_name
205    
206     class Meta:
207         unique_together = (("group", "template_name"),)
208
209
210
211 class PermissionFlag(models.Model):
212     """
213     Permission flags are predefined (in the code) flags of user rights.
214     Very similar to django's permissions.
215
216     (I decided against using django's permissions for the sake of simplicity..
217     i don't like the idea of auto generating permissions which aren't used
218     in the application code (but only within the django administration))
219     """
220     name = models.CharField( max_length = 250, unique = True )
221
222
223     sph_permission_flags = { 'group_administrator':
224                              'Users with this permission flag have all permissions.',
225
226                              'community_manage_roles':
227                              'User have permission to create, edit and assign roles.',
228                              }
229
230
231     def __unicode__(self):
232         return self.name
233
234 class Role(models.Model):
235     """
236     A role is a user defined collection of so called permission flags.
237     """
238     name = models.CharField( max_length = 250 )
239     group = models.ForeignKey( Group )
240
241     permission_flags = models.ManyToManyField( PermissionFlag, related_name = 'roles' )
242
243
244     def get_permission_flag_string(self):
245         return ", ".join( [flag.name for flag in self.permission_flags.all()] )
246
247     def __unicode__(self):
248         return '%s - %s' % (self.group.name, self.name)
249
250     def get_absolute_editurl(self):
251         return ('sphene.community.views.admin_permission_role_edit', (), { 'groupName': self.group.name, 'role_id': self.id, } )
252     get_absolute_editurl = sphpermalink(get_absolute_editurl)
253
254     def get_absolute_memberlisturl(self):
255         return ('sphene.community.views.admin_permission_role_member_list', (), { 'groupName': self.group.name, 'role_id': self.id, } )
256     get_absolute_memberlisturl = sphpermalink(get_absolute_memberlisturl)
257
258     def get_absolute_memberaddurl(self):
259         return ('sphene.community.views.admin_permission_role_member_add', (), { 'groupName': self.group.name, 'role_id': self.id, } )
260     get_absolute_memberaddurl = sphpermalink(get_absolute_memberaddurl)
261
262     def get_absolute_groupmemberaddurl(self):
263         return ('sphene.community.views.admin_permission_role_groupmember_add', (), { 'groupName': self.group.name, 'role_id': self.id, } )
264     get_absolute_groupmemberaddurl = sphpermalink(get_absolute_groupmemberaddurl)
265
266     class Meta:
267         unique_together = (('name', 'group'),)
268
269
270
271 class RoleMember(models.Model):
272     """
273     A role member is the relation between a given role and a
274     1.) user OR 2.) rolegroup - one of those two have to be null !
275
276     This relation can have additional limitations - e.g. for the board
277     it might only be active within one given category -
278     see RoleMemberLimitation.
279
280     If there are no limitations (has_limitations = False) then the role
281     is active for the user globally within the role's group.
282     """
283     role = models.ForeignKey( Role )
284     user = models.ForeignKey( User, null = True )
285     rolegroup = models.ForeignKey( 'RoleGroup', null = True )
286
287     has_limitations = models.BooleanField()
288
289
290     changelog = ( ( '2008-04-15 00', 'alter', 'ALTER user_id DROP NOT NULL', ),
291                   ( '2008-04-15 01', 'alter', 'ADD rolegroup_id integer REFERENCES community_rolegroup(id)', ), )
292
293     def get_limitations_string(self):
294         if not self.has_limitations:
295             return "None"
296         limitation = self.rolememberlimitation_set.get()
297         return "%s: %s" % (limitation.object_type.model_class()._meta.object_name, unicode(limitation.content_object))
298
299
300
301 class RoleMemberLimitation(models.Model):
302     """
303     Limits the membership of a user to a role by only applying to a
304     specific object.
305     """
306     role_member = models.ForeignKey( RoleMember )
307
308     object_type = models.ForeignKey(ContentType)
309     object_id = models.PositiveIntegerField(db_index = True)
310
311     content_object = generic.GenericForeignKey(ct_field = 'object_type')
312
313     class Meta:
314         unique_together = (('role_member', 'object_type', 'object_id'),)
315
316
317
318
319 class RoleGroup(models.Model):
320     """
321     a role group can be used to add common restrictions for a given group
322     of users.
323     """
324     group = models.ForeignKey(Group)
325     name = models.CharField(max_length = 250)
326
327     def __unicode__(self):
328         return self.name
329
330     def get_absolute_editurl(self):
331         return ('sphene.community.views.admin_permission_rolegroup_edit', (), { 'groupName': self.group.name, 'rolegroup_id': self.id, } )
332     get_absolute_editurl = sphpermalink(get_absolute_editurl)
333
334     class Meta:
335         unique_together = ('group', 'name',)
336
337
338 class RoleGroupMember(models.Model):
339     rolegroup = models.ForeignKey(RoleGroup)
340     user = models.ForeignKey(User)
341
342     class Meta:
343         unique_together = ('rolegroup', 'user',)
344
345
346
347 #########################################################
348 ###
349 ### tagging
350 ###
351 ### Tagging is in part inspired by django-tagging:
352 ###    http://code.google.com/p/django-tagging/
353 ### (but because of various reasons - e.g. that i want tags separated by group -
354 ###  i decided to implement tagging from scratch)
355
356 tag_sanitize_regex = re.compile(r'[^\w]+', re.S | re.U)
357
358 def tag_sanitize(tag_label):
359     return tag_sanitize_regex.sub('', tag_label).lower()
360
361 def tag_set_labels(model_instance, tag_labels):
362     """
363     sets the tags of the given model_instance (which must exists already!)
364     to the given tag labels (which must be TagLabel models.)
365     - removes all existing tags.
366
367     returns True if anything has changed, False otherwise.
368     """
369
370     model_type = ContentType.objects.get_for_model(model_instance)
371
372     # Check if anything has changed
373     tag_labels = list(tag_labels)
374     old_tag_labels = TaggedItem.objects.filter( content_type__pk = model_type.id,
375                                                 object_id = model_instance.id, )
376
377     if len(tag_labels) == old_tag_labels.count():
378         if len(tag_labels) == 0:
379             # Nothing has changed
380             return False
381
382         for tagged_item in old_tag_labels:
383             found = False
384             for tag_label in tag_labels:
385                 if tag_label == tagged_item.tag_label:
386                     found = True
387                     tag_labels.remove(tag_label)
388                     break
389
390             if not found:
391                 break
392
393         if len(tag_labels) == 0:
394             # We started with the same amount of tags and all are
395             # the same ... ie. they did not change.
396             return False
397
398
399     # First remove existing tag labels
400     TaggedItem.objects.filter( content_type__pk = model_type.id,
401                                object_id = model_instance.id, ).delete()
402
403    
404
405     for tag_label in tag_labels:
406         t = TaggedItem( object = model_instance,
407                         tag_label = tag_label )
408         t.save()
409
410
411     return True
412
413
414 def tag_get_labels(model_instance):
415     """
416     returns all TagLabel objects for the given model_instance.
417     """
418     model_type = ContentType.objects.get_for_model(model_instance)
419     tagged_items = TaggedItem.objects.filter( content_type__pk = model_type.id,
420                                               object_id = model_instance.id, )
421
422     tag_labels = list()
423     for tagged_item in tagged_items:
424         tag_labels.append( tagged_item.tag_label )
425
426     return tag_labels
427
428 def tag_get_or_create_label(group, tag_label_str):
429     if tag_label_str == '':
430         # ignore empty labels
431         return None
432    
433     # Check if the label is already known:
434     try:
435         tag_label = TagLabel.objects.get( tag__group = group,
436                                           label__exact = tag_label_str )
437     except TagLabel.DoesNotExist:
438         # TagLabel not found, search for an appropriate tag
439         tag_name_str = tag_sanitize(tag_label_str)
440         # Find tag
441         try:
442             tag = Tag.objects.get( group = group,
443                                    name__exact = tag_name_str )
444         except Tag.DoesNotExist:
445             tag = Tag( group = group,
446                        name = tag_name_str )
447             tag.save()
448
449         tag_label = TagLabel( tag = tag,
450                               label = tag_label_str )
451         tag_label.save()
452
453     return tag_label
454
455
456 qn = connection.ops.quote_name
457
458 def get_queryset_and_model(queryset_or_model):
459     try:
460         return queryset_or_model, queryset_or_model.model
461     except AttributeError:
462         return queryset_or_model._default_manager.all(), queryset_or_model
463
464 def tag_get_models_by_tag(queryset_or_model, tag):
465     # pretty much copied from django-tagging
466     queryset, model = get_queryset_and_model(queryset_or_model)
467     content_type = ContentType.objects.get_for_model(model)
468     opts = TaggedItem._meta
469     tagged_item_table = qn(opts.db_table)
470     tag_label_table = qn(TagLabel._meta.db_table)
471     return queryset.extra(
472         tables=[TaggedItem._meta.db_table,
473                TagLabel._meta.db_table, ],
474         where=[
475             '%s.content_type_id = %%s' % tagged_item_table,
476             '%s.tag_id = %%s' % tag_label_table,
477             '%s.tag_label_id = %s.%s' % (tagged_item_table,
478                                          tag_label_table,
479                                          TagLabel._meta.pk.column),
480             '%s.%s = %s.object_id' % (qn(model._meta.db_table),
481                                       qn(model._meta.pk.column),
482                                       tagged_item_table)
483             ],
484         params=[content_type.pk, tag.pk],
485         )
486
487 class Tag(models.Model):
488     """
489     A tag is the internal representation which is always linked to a specific group.
490
491     A tag name only allows alpha numeric characters without spaces, etc. and only stores
492     lower case letters !
493     """
494     group = models.ForeignKey( Group )
495     name = models.CharField( max_length = 250, )
496
497     def __unicode__(self):
498         return self.name
499
500     class Meta:
501         unique_together = (("group", "name"))
502
503 class TagLabel(models.Model):
504     """
505     A tag label represents the user entered value for the tag. Including uppercase/lowercase,
506     and all characters usually not allowed within a tag.
507     """
508     tag = models.ForeignKey(Tag, related_name = 'labels')
509     label = models.CharField( max_length = 250, )
510
511     def __unicode__(self):
512         return self.label
513
514     class Meta:
515         unique_together = (("tag", "label"))
516
517 class TaggedItem(models.Model):
518     """
519     Relationship between a tag label and an item.
520     """
521     tag_label = models.ForeignKey(TagLabel, related_name = 'items')
522     content_type = models.ForeignKey(ContentType, related_name = 'sph_taggeditem_set')
523     object_id = models.PositiveIntegerField(db_index=True)
524     object = generic.GenericForeignKey('content_type', 'object_id')
525
526     class Meta:
527         unique_together = (('tag_label', 'content_type', 'object_id'))
528
529 #########################################################
530 ###
531 ### hooks
532 ###
533
534 from django import forms
535 from sphene.community.forms import EditProfileForm, Separator
536 from sphene.community.signals import profile_edit_init_form, profile_edit_save_form, profile_display
537
538
539 def get_public_emailaddress_help():
540     # TODO also add a notice about wether anonymous user require to enter a captcha ?
541     if get_sph_setting( 'community_email_show_only_public' ):
542         return _('This email address will be shown to all users. If you leave it black noone will see your email address.')
543     return _('This email address will be shown to all users. If you leave it blank, your verified email address will be shown.')
544
545 def get_user_displayname_help():
546     if get_sph_setting( 'community_user_displayname_fallback' ) == 'username':
547         return _('This display name will be shown to all users. If you leave it blank then your username will be shown.')
548     return _('This display name will be shown to all users. If you leave it blank, your first and last name will be shown. If those are blank too, then your username will be shown.')
549
550
551 def community_profile_edit_init_form(sender, instance, signal, request, *args, **kwargs):
552     user = instance.user
553     try:
554         profile = CommunityUserProfile.objects.get( user = user, )
555     except CommunityUserProfile.DoesNotExist:
556         profile = CommunityUserProfile( user = user, )
557        
558     instance.fields['community_settings'] = Separator(label=_(u'Community settings'))
559     instance.fields['displayname'] = forms.CharField( label = _(u'Display name'),
560                                                       required = False,
561                                                       initial = profile.displayname,
562                                                       help_text = get_user_displayname_help())
563    
564     instance.fields['public_emailaddress'] = forms.CharField( label = _(u'Public email address'),
565                                                               required = False,
566                                                               initial = profile.public_emailaddress,
567                                                               help_text = get_public_emailaddress_help())
568
569
570
571
572     fields = CommunityUserProfileField.objects.all()
573     for field in fields:
574         initial = ''
575         if profile.id:
576             try:
577                 value = CommunityUserProfileFieldValue.objects.get( user_profile = profile,
578                                                                     profile_field = field, )
579                 initial = value.value
580             except CommunityUserProfileFieldValue.DoesNotExist:
581                 pass
582                                                    
583         instance.fields['community_field_%d' % field.id] = forms.RegexField( regex = field.regex,
584                                                                              label = field.name,
585                                                                              help_text = field.help_text,
586                                                                              initial = initial,
587                                                                              required = False,
588                                                                              )
589
590 def community_profile_edit_save_form(sender, instance, signal, request, *args, **kwargs):
591     data = instance.cleaned_data
592     user = instance.user
593     try:
594         profile = CommunityUserProfile.objects.get( user = user, )
595     except CommunityUserProfile.DoesNotExist:
596         profile = CommunityUserProfile( user = user, )
597
598     profile.public_emailaddress = data['public_emailaddress']
599     profile.displayname = data['displayname']
600     profile.save()
601
602     fields = CommunityUserProfileField.objects.all()
603     for field in fields:
604         try:
605             value = CommunityUserProfileFieldValue.objects.get( user_profile = profile,
606                                                                 profile_field = field, )
607             initial = value.value
608         except CommunityUserProfileFieldValue.DoesNotExist:
609             value = CommunityUserProfileFieldValue( user_profile = profile,
610                                                     profile_field = field, )
611         newvalue = data['community_field_%d' % field.id]
612         if newvalue:
613             value.value = data['community_field_%d' % field.id]
614             value.save()
615         else:
616             if value.id: value.delete()
617    
618     request.user.message_set.create( message = _("Successfully saved community profile.") )
619
620 def community_profile_display(sender, signal, request, user, **kwargs):
621     try:
622         profile = CommunityUserProfile.objects.get( user = user, )
623     except CommunityUserProfile.DoesNotExist:
624         return None
625
626     ret = ''
627     fields = CommunityUserProfileField.objects.all()
628     for field in fields:
629         try:
630             value = CommunityUserProfileFieldValue.objects.get( user_profile = profile,
631                                                                 profile_field = field, )
632             formatstring = '<tr><th>%(label)s</th><td>' + (field.renderstring or '%(value)s') + '</td></tr>'
633             ret += (formatstring % { 'label': field.name,
634                                      'value': value.value, })
635         except CommunityUserProfileFieldValue.DoesNotExist:
636             continue
637                                            
638     return ret
639
640 profile_edit_init_form.connect(community_profile_edit_init_form, sender = EditProfileForm)
641 profile_edit_save_form.connect(community_profile_edit_save_form, sender = EditProfileForm)
642 profile_display.connect(community_profile_display)
643
644
Note: See TracBrowser for help on using the browser.