""" base stuff for tagging """ __revision__ = "$Rev$" __date__ = "$Date$" from django.db import models, connection from django.contrib.sites.models import Site from django.db.models.fields import FieldDoesNotExist from django.contrib.sites.managers import CurrentSiteManager from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType from django.conf import settings #from django.db.models.fields.related import ManyToManyField from django.template.defaultfilters import slugify from django.core.cache import cache #, ForeignKey, ForeignRelatedObjectsDescriptor # from django.utils.translation import gettext_lazy as _ from django.db.models import signals from django.dispatch import dispatcher from datetime import datetime from zilbo.common.utils.middleware.threadlocals import get_current_user from zilbo.common.utils.smallmanager import CacheManager import shlex #import inspect class TagManager(models.Manager): def __init__(self, field_name='site'): super(TagManager, self).__init__() self.__field_name = field_name self.__is_validated = False def get_query_set(self): if not self.__is_validated: try: self.model._meta.get_field(self.__field_name) except FieldDoesNotExist: raise ValueError, "%s couldn't find a field named %s in %s." % \ (self.__class__.__name__, self.__field_name, self.model._meta.object_name) self.__is_validated = True return super(TagManager, self).get_query_set().filter(**{self.__field_name + '__id__exact': settings.SITE_ID}) def get_from_string(self, tags ): taglist=shlex.split( tags.replace(',',' ') ) fields=[] for i in range(0, len(taglist)): fields.append("%s") if len(fields) <= 0: return [] cursor = connection.cursor() cursor.execute( "select id from tag where name in (%s) and site_id = %s" % ( ",".join(fields), settings.SITE_ID), taglist ) rows = cursor.fetchall() ids = [] for id in rows: ids.append(id[0]) return Tag.objects.in_bulk( ids ).values() def get_from_slug_string(self, tags ): taglist=shlex.split( tags.replace(',',' ') ) fields=[] for i in range(0, len(taglist)): fields.append("%s") if len(fields) <= 0: return [] cursor = connection.cursor() cursor.execute( "select id from tag where slug in (%s) and site_id = %s" % ( ",".join(fields), settings.SITE_ID), taglist ) rows = cursor.fetchall() ids = [] for id in rows: ids.append(id[0]) return Tag.objects.in_bulk( ids ).values() def create_from_strings(self, user, taglist ): tags=[] for tag_name in taglist: if tag_name.strip() != "": slug = slugify( tag_name ) try: tag = Tag.objects.get(slug = slug ) except Tag.DoesNotExist: tag = Tag( name=tag_name.strip(), slug= slug, last_change_user= user, create_user=user, last_change= datetime.now(), creation_date=datetime.now() ) tag.save() tags.append(tag) return tags def create_from_string(self, user, tagstring ): taglist=shlex.split( tagstring.replace(',', ' ') ) #taglist=tagstring.split(" ") tags={} for tag_name in taglist: if tag_name.strip() != "": slug = slugify( tag_name ) try: # try: tag = Tag.objects.get(slug = slug ) # except AssertionError: # tag = Tag.objects.filter(slug = slug )[0] except Tag.DoesNotExist: tag = Tag( name=tag_name.strip(), slug=slug, last_change_user= user, create_user=user, last_change= datetime.now(), creation_date=datetime.now() ) tag.save() tags[tag.id]=tag # tags.append(tag) return tags.values() class Tag(models.Model): """ A 'Tag' is similar to a taxonomy, where people (either users, or a editor) assign multiple keywords to a given object. """ name = models.CharField(maxlength=40, core=True, db_index=True ) slug = models.SlugField(maxlength=40, prepopulate_from=("name",), db_index=True ) #description = models.CharField(maxlength=200, blank=True) site = models.ForeignKey(Site, default=settings.SITE_ID, blank=True) last_change_user = models.ForeignKey(User, blank=True, related_name='tag_last_change' ) last_change = models.DateTimeField('last changed', auto_now=True) create_user = models.ForeignKey(User, blank=True, related_name='tag_create_user' ) creation_date = models.DateTimeField('creation date', blank=True) objects = TagManager() class Admin: list_display = ('name', 'site' ) search_fields = ['name'] class Meta: ordering = [ 'name' ] unique_together = (('site', 'name' ),( 'site','slug'), ) db_table ='tag' def __str__(self): # if (self.name.find(" " ) > 0 ): # return "'%s'" % self.name # else: return self.name; def get_absolute_url(self): return "/tag/%s/" % ( self.slug ) def save(self): self.site = settings.SITE_ID self.site_id = settings.SITE_ID if not self.slug: self.slug = slugify( self.name ) super(Tag, self).save() def get_tags_for(cls, content_type_id, object_id ): user = get_current_user() u={} if not user.is_anonymous: # u = cache.get('tags.get_tags_for_u-%s-%s-%s' % ( user.id, content_type_id, object_id ) ) if u is None: u={} user_tags = TagUserObject.objects.filter( content_type__id__exact = content_type_id, object_id__exact= object_id ) for i in user_tags: u[i.tag.name ] = ( i.tag, i.weight ) # cache.set( 'tags.get_tags_for_u-%s-%s-%s' % ( user.id, content_type_id, object_id ), u, 600) # summary = cache.get( 'tags.get_tags_for-%s-%s' % ( content_type_id, object_id )) summary=None if summary is None: summary={} summary_tags = TagObject.objects.filter( content_type__id__exact = content_type_id, object_id__exact= object_id ) for i in summary_tags: summary[i.tag.name] = ( i.tag, i.weight ) # cache.set( 'tags.get_tags_for-%s-%s' % ( content_type_id, object_id ), summary, 900) s={} for i in summary: if not u.has_key( i ): s[i] = summary[i] return (u.values(),s.values()) get_tags_for = classmethod(get_tags_for) def cloud(cls,content_type_id=None, object_id= None, limit=40, buckets=9): cursor = connection.cursor() if content_type_id: if object_id: cursor.execute ( "select tag_id, sum(weight) from tag_object where content_type_id = %s and object_id = %s and site_id =%s and weight >0 group by tag_id order by 2 desc LIMIT %s", [ content_type_id, object_id, settings.SITE_ID, limit ]) else: cursor.execute ( "select tag_id, sum(weight) from tag_object where content_type_id = %s and site_id =%s and weight >0 group by tag_id order by 2 desc LIMIT %s", [ content_type_id, settings.SITE_ID, limit ]) else: cursor.execute ( "select tag_id, sum(weight) from tag_object where site_id = %s and weight >0 group by tag_id order by 2 desc LIMIT %s", [ settings.SITE_ID, limit ]) rows = cursor.fetchall() weights = {} min=9999999 max=0 for id in rows: weights[id[0]] = id[1] if id[1]> max: max=id[1] if id[1]< min: min=id[1] diff = max - min increment = diff / buckets bucket={} for i in weights: bucket[i] = int(( weights[i]- min ) / increment ) tags = Tag.objects.in_bulk ( bucket.keys() ) l={} for i in tags: l[tags[i]]= bucket[i]+1 return l cloud = classmethod( cloud ) #class TagObjectManager(models.Manager): class TagObjectManager(CurrentSiteManager): def set_bulk_tags( self, tag, content_type, keys): for k in keys: new_obj = TagObject( tag= tag, content_type = content_type, object_id = k ) new_obj.save() def __str__(self): qs = self.get_query_set() a=[] for i in qs : name = i.tag.name if name.find(" " ) > 0: name = "'" + name + "'" a.append( name ) return ", ".join(a) class TagObject( models.Model): tag = models.ForeignKey(Tag, related_name = 'tagged_objects') content_type = models.ForeignKey(ContentType) object_id = models.PositiveIntegerField('object ID') weight = models.IntegerField(default=1) site = models.ForeignKey(Site, default=settings.SITE_ID, blank=True) objects = TagObjectManager() content_object = models.GenericForeignKey() class Meta: db_table ='tag_object' unique_together = (('tag', 'content_type','object_id' ),) ordering = [ 'content_type', 'object_id', '-weight' ] def __str__(self): return "%s - %s:%d" % (self.tag, self.content_type, self.object_id) def save(self): self.site = settings.SITE_ID self.site_id = settings.SITE_ID super(TagObject, self).save() def source_object(self): return self.content_type.get_object_for_this_type(pk=self.object_id) def get_tagged_models(cls): """ get a list of objects currently tagged """ cursor = connection.cursor() cursor.execute ( "select distinct content_type_id from tag_object where site_id = %s", [ settings.SITE_ID]) rows = cursor.fetchall() ids = [] for id in rows: ids.append(id[0]) return ContentType.objects.in_bulk( ids ) get_tagged_models = classmethod(get_tagged_models) def get_tags_for_model(cls, content_type): """ get a list of tags which have been used for the given content_type""" tags = cache.get('get_tags_for_model_%s_%s' % ( content_type.id, settings.SITE_ID )) if tags is None: cursor = connection.cursor() cursor.execute ( "select distinct tag_id from tag_object where content_type_id = %s and site_id = %s", [ content_type.id, settings.SITE_ID ]) rows = cursor.fetchall() ids = [] for id in rows: ids.append(id[0]) tags = Tag.objects.in_bulk( ids ) cache.set( 'get_tags_for_model_%s_%s' % ( content_type.id, settings.SITE_ID), tags, 1800 ) return tags get_tags_for_model = classmethod(get_tags_for_model) def get_tags_matching(cls, content_type, objects): """ get a list of objects currently tagged """ ids = [] for id in objects.values(): ids.append( str(id) ) cursor = connection.cursor() cursor.execute ( "select distinct tag_id from tag_object where content_type_id = %%s and site_id = %%s and object_id in (%s)" % ",".join(ids) , [content_type.id, settings.SITE_ID ] ) rows = cursor.fetchall() tags=[] for id in rows: tags.append(id[0]) return Tag.objects.in_bulk( tags ) get_tags_matching = classmethod(get_tags_matching) def intersection( cls, tags, content_type ): """ return a list of objects which have ALL the tags defined on them """ filter = {'content_type__id__exact' : content_type.id } possible={} objects={} object_list=[] count=0 for tag in tags: count += 1 filter['tag__id__exact' ] = tag.id list = cls.objects.filter( **filter ) for obj in list: if objects.has_key( obj.object_id ): possible[ obj.object_id ] += 1 else: objects[ obj.object_id ] = obj.object_id possible[ obj.object_id ] = 1 for ( id, obj_count) in possible.items(): if obj_count == count: object_list.append(objects[id]) model_class = content_type.model_class() return model_class.objects.in_bulk( object_list ) # return object_list intersection = classmethod(intersection ) class TagUserObjectManager(CurrentSiteManager): def set_bulk_tags( self, tag, content_type, user, keys): for k in keys: new_obj = TagUserObject( tag= tag, user=user, content_type = content_type, object_id = k ) new_obj.save() def get_query_set(self): u = get_current_user() if u is None or u.is_anonymous(): return super(TagUserObjectManager, self).get_query_set().filter(user__id__exact = 0 ) else: return super(TagUserObjectManager, self).get_query_set().filter(user__id__exact = u.id ) def __str__(self): qs = self.get_query_set() a=[] for i in qs : name = i.tag.name if name.find(" " ) > 0: name = "'" + name + "'" a.append( name ) return ", ".join(a) class TagUserObject( models.Model): tag = models.ForeignKey(Tag, related_name = 'tagged_user_objects', db_index=True) user = models.ForeignKey(User) content_type = models.ForeignKey(ContentType) object_id = models.PositiveIntegerField('object ID') site = models.ForeignKey(Site, default=settings.SITE_ID, blank=True) weight = models.IntegerField(default=1) objects = TagUserObjectManager() all_objects = models.Manager() content_object = models.GenericForeignKey() class Meta: db_table ='tag_user_object' unique_together = (('tag', 'user', 'content_type','object_id' ),) ordering = [ 'user', 'content_type', 'object_id', 'tag' ] def source_object(self): return self.content_type.get_object_for_this_type(pk=self.object_id) def __str__(self): return "%s -(%s) %s:%d" % (self.tag, self.user, self.content_type, self.object_id) def save(self): self.site = settings.SITE_ID self.site_id = settings.SITE_ID super(TagUserObject, self).save() def get_tagged_models(cls): """ get a list of objects currently tagged at the user level""" cursor = connection.cursor() cursor.execute ( "select distinct content_type_id from tag_user_object where site_id = %s", [ settings.SITE_ID ] ) rows = cursor.fetchall() ids = [] for id in rows: ids.append(id[0]) return ContentType.objects.in_bulk( ids ) get_tagged_models = classmethod(get_tagged_models) def get_tags_for_model(cls, content_type): """ get a list of tags which have been used for the given content_type""" cursor = connection.cursor() user = get_current_user() cursor.execute ( "select distinct tag_id from tag_user_object where user_id = %s and content_type_id = %s and site_id = %s", [user.id, content_type.id, settings.SITE_ID ]) rows = cursor.fetchall() ids = [] for id in rows: ids.append(id[0]) return Tag.objects.in_bulk( ids ) get_tags_for_model = classmethod(get_tags_for_model) def get_tags_matching(cls, user, content_type, objects): """ get a list of objects currently tagged """ ids = [] for id in objects.values(): ids.append( str(id) ) cursor = connection.cursor() cursor.execute ( "select distinct tag_id from tag_user_object where user_id= %%s and content_type_id = %%s and site_id = %%s and object_id in (%s)" % ",".join(ids) , [user.id, content_type.id, settings.SITE_ID ] ) rows = cursor.fetchall() tags=[] for id in rows: tags.append(id[0]) return Tag.objects.in_bulk( tags ) get_tags_matching = classmethod(get_tags_matching) def intersection( cls, tags, content_type ): """ return a list of objects which have ALL the tags defined on them for the specific user """ filter = { 'content_type__id__exact': content_type.id } possible={} objects={} object_list=[] count=0 for tag in tags: count += 1 filter['tag__id__exact' ] = tag.id list = TagUserObject.objects.filter( **filter ) for obj in list: objects[ obj.object_id ] = obj.object_id try: possible[ obj.object_id ] += 1 except KeyError: possible[ obj.object_id ] = 1 for ( id, obj_count) in possible.items(): if obj_count == count: object_list.append(objects[id]) model_class = content_type.model_class() return model_class.objects.in_bulk( object_list ) intersection = classmethod(intersection ) class BookMark(models.Model): """ A simple 'bookmark' table so users can just mark an object as interesting, without tagging it """ user = models.ForeignKey(User) content_type = models.ForeignKey(ContentType) object_id = models.IntegerField(_('object ID')) headline = models.CharField(_('headline'), maxlength=255 ) description = models.TextField(_('description') ) site = models.ForeignKey(Site, default=settings.SITE_ID) last_change_user = models.ForeignKey(User, blank=True, related_name='bookmark_last_change' ) last_change = models.DateTimeField('last changed', auto_now=True) create_user = models.ForeignKey( User, blank=True, related_name='bookmark_create' ) creation_date = models.DateTimeField('creation date', blank=True) objects = CurrentSiteManager() class Admin: list_display = ( 'user', 'headline') class Meta: unique_together = (('site', 'user','content_type','object_id' ),) db_table ='bookmark' def get_absolute_url(self): return "/bookmark/%d/" % ( self.id ) def __str__(self): return "%s:%s" ( self.content_type, self.headline ) def get_content_object(self): """ Returns the object that this comment is a comment on. Returns None if the object no longer exists. """ from django.core.exceptions import ObjectDoesNotExist try: return self.content_type.get_object_for_this_type(pk=self.object_id) except ObjectDoesNotExist: return None get_content_object.short_description = _('Content object') def get_crossdomain_url(self): return "/r/%d/%d/" % (self.content_type_id, self.object_id) def increment_tag_summary(sender, instance, signal, *args, **kwargs): """ when a user tag is added .. increment/create the summary reference """ if instance.id == None: try: ictyid = instance.content_type.id tag = TagObject.objects.get( tag__id__exact = instance.tag.id, content_type__id__exact = instance.content_type.id, object_id = instance.object_id ) tag.weight += 1 except TagObject.DoesNotExist: tag = TagObject( tag = instance.tag, content_type = instance.content_type, object_id = instance.object_id, weight = 1 ) tag.save() def decrement_tag_summary(sender, instance, signal, *args, **kwargs): """ when a user tag is deleted.. decrement the weight/delete the summary reference """ if instance.id == None: try: tag = TagObject.objects.get( tag__id__exact = instance.tag.id, content_type__id__exact = instance.content_type.id, object_id = instance.object_id ) if tag.weight <= 1: tag.delete() else: tag.weight -= 1 tag.save() except TagObject.DoesNotExist: pass dispatcher.connect( increment_tag_summary , signal=signals.pre_save, sender=TagUserObject ) dispatcher.connect( decrement_tag_summary , signal=signals.post_delete, sender=TagUserObject ) EXTERNAL_BOOKMARK_CHOICES = ( ( 'D', 'digger' ), ( 'B', 'bookmarker'), ) class ExternalBookMark(models.Model): name = models.CharField( maxlength=20, db_index=True, unique=True) icon = models.ImageField(upload_to='extbookmark' ) URL = models.URLField(verify_exists=False, help_text='switch XXUXX for where the URL would go, and XXTXX where the title should go in this URL') type = models.CharField(choices=EXTERNAL_BOOKMARK_CHOICES, default='B', maxlength=1) def __str__(self): return self.name class Admin: list_display = ('name', 'type','icon') ordering = [ 'type','name']