class UserProperty(BaseModel): user = models.ForeignKey(User, related_name='properties') key = models.CharField(max_length=16) value = PickledObjectField() class Meta: app_label = 'forum' unique_together = ('user', 'key') def cache_key(self): return self._generate_cache_key("%s:%s" % (self.user.id, self.key)) @classmethod def infer_cache_key(cls, querydict): if 'user' in querydict and 'key' in querydict: return cls._generate_cache_key( "%s:%s" % (querydict['user'].id, querydict['key'])) return None
class Node(BaseModel, NodeContent): __metaclass__ = NodeMetaClass node_type = models.CharField(max_length=16, default='node') parent = models.ForeignKey('Node', related_name='children', null=True) abs_parent = models.ForeignKey('Node', related_name='all_children', null=True) added_at = models.DateTimeField(default=datetime.datetime.now) score = models.IntegerField(default=0) state_string = models.TextField(default='') last_edited = models.ForeignKey('Action', null=True, unique=True, related_name="edited_node") last_activity_by = models.ForeignKey(User, null=True) last_activity_at = models.DateTimeField(null=True, blank=True) tags = models.ManyToManyField('Tag', related_name='%(class)ss') active_revision = models.OneToOneField('NodeRevision', related_name='active', null=True) extra = PickledObjectField() extra_ref = models.ForeignKey('Node', null=True) extra_count = models.IntegerField(default=0) marked = models.BooleanField(default=False) comment_count = DenormalizedField("children", node_type="comment", canceled=False) flag_count = DenormalizedField("flags") friendly_name = _("post") objects = NodeManager() def __unicode__(self): return self.headline @classmethod def _generate_cache_key(cls, key, group="node"): return super(Node, cls)._generate_cache_key(key, group) @classmethod def get_type(cls): return cls.__name__.lower() @property def leaf(self): leaf_cls = NodeMetaClass.types.get(self.node_type, None) if leaf_cls is None: return self leaf = leaf_cls() leaf.__dict__ = self.__dict__ return leaf @property def nstate(self): state = self.__dict__.get('_nstate', None) if state is None: state = NodeStateDict(self) self._nstate = state return state @property def nis(self): nis = self.__dict__.get('_nis', None) if nis is None: nis = NodeStateQuery(self) self._nis = nis return nis @property def last_activity(self): try: return self.actions.order_by('-action_date')[0].action_date except: return self.last_seen @property def state_list(self): return [s.state_type for s in self.states.all()] @property def deleted(self): return self.nis.deleted @property def absolute_parent(self): if not self.abs_parent_id: return self return self.abs_parent @property def summary(self): content = strip_tags(self.html)[:SUMMARY_LENGTH] # Remove multiple spaces. content = re.sub(' +', ' ', content) # Remove line breaks. We don't need them at all. content = content.replace("\n", '') return content @models.permalink def get_revisions_url(self): return ('revisions', (), {'id': self.id}) def update_last_activity(self, user, save=False, time=None): if not time: time = datetime.datetime.now() self.last_activity_by = user self.last_activity_at = time if self.parent: self.parent.update_last_activity(user, save=True, time=time) if save: self.save() def _create_revision(self, user, number, **kwargs): revision = NodeRevision(author=user, revision=number, node=self, **kwargs) revision.save() return revision def create_revision(self, user, **kwargs): number = self.revisions.aggregate( last=models.Max('revision'))['last'] + 1 revision = self._create_revision(user, number, **kwargs) self.activate_revision(user, revision) return revision def activate_revision(self, user, revision): self.title = revision.title self.tagnames = revision.tagnames self.body = self.rendered(revision.body) self.active_revision = revision self.update_last_activity(user) self.save() def get_active_users(self, active_users=None): if not active_users: active_users = set() active_users.add(self.author) for node in self.children.all(): if not node.nis.deleted: node.get_active_users(active_users) return active_users def get_last_edited(self): if not self.last_edited: try: le = self.actions.exclude( action_type__in=('voteup', 'votedown', 'flag'), canceled=True).order_by('-action_date')[0] self.last_edited = le self.save() except: pass return self.last_edited def _list_changes_in_tags(self): dirty = self.get_dirty_fields() if not 'tagnames' in dirty: return None else: if self._original_state['tagnames']: old_tags = set(self._original_state['tagnames'].split()) else: old_tags = set() new_tags = set(self.tagnames.split()) return dict(current=list(new_tags), added=list(new_tags - old_tags), removed=list(old_tags - new_tags)) def _last_active_user(self): return self.last_edited and self.last_edited.by or self.author def _process_changes_in_tags(self): tag_changes = self._list_changes_in_tags() if tag_changes is not None: for name in tag_changes['added']: try: tag = Tag.objects.get(name=name) except Tag.DoesNotExist: tag = Tag.objects.create( name=name, created_by=self._last_active_user()) if not self.nis.deleted: tag.add_to_usage_count(1) tag.save() if not self.nis.deleted: for name in tag_changes['removed']: try: tag = Tag.objects.get(name=name) tag.add_to_usage_count(-1) tag.save() except: pass return True return False def mark_deleted(self, action): self.nstate.deleted = action self.save() if action: for tag in self.tags.all(): tag.add_to_usage_count(-1) tag.save() else: for tag in Tag.objects.filter(name__in=self.tagname_list()): tag.add_to_usage_count(1) tag.save() def delete(self, *args, **kwargs): for tag in self.tags.all(): tag.add_to_usage_count(-1) tag.save() self.active_revision = None self.save() for n in self.children.all(): n.delete() for a in self.actions.all(): a.cancel() super(Node, self).delete(*args, **kwargs) def save(self, *args, **kwargs): if not self.id: self.node_type = self.get_type() super(BaseModel, self).save(*args, **kwargs) self.active_revision = self._create_revision( self.author, 1, title=self.title, tagnames=self.tagnames, body=self.body) self.activate_revision(self.author, self.active_revision) self.update_last_activity(self.author, time=self.added_at) if self.parent_id and not self.abs_parent_id: self.abs_parent = self.parent.absolute_parent tags_changed = self._process_changes_in_tags() super(Node, self).save(*args, **kwargs) if tags_changed: self.tags = list(Tag.objects.filter(name__in=self.tagname_list())) class Meta: app_label = 'forum'
class Action(BaseModel): user = models.ForeignKey('User', related_name="actions") real_user = models.ForeignKey('User', related_name="proxied_actions", null=True) ip = models.CharField(max_length=16) node = models.ForeignKey('Node', null=True, related_name="actions") action_type = models.CharField(max_length=16) action_date = models.DateTimeField(default=datetime.datetime.now) extra = PickledObjectField() canceled = models.BooleanField(default=False) canceled_by = models.ForeignKey('User', null=True, related_name="canceled_actions") canceled_at = models.DateTimeField(null=True) canceled_ip = models.CharField(max_length=16) hooks = {} objects = ActionManager() @property def at(self): return self.action_date @property def by(self): return self.user def repute_users(self): pass def process_data(self, **data): pass def process_action(self): pass def cancel_action(self): pass @property def verb(self): return "" def describe(self, viewer=None): return self.__class__.__name__ def get_absolute_url(self): if self.node: return self.node.get_absolute_url() else: return self.user.get_profile_url() def repute(self, user, value): repute = ActionRepute(action=self, user=user, value=value) repute.save() return repute def cancel_reputes(self): for repute in self.reputes.all(): cancel = ActionRepute(action=self, user=repute.user, value=(-repute.value), by_canceled=True) cancel.save() def leaf(self): leaf_cls = ActionProxyMetaClass.types.get(self.action_type, None) if leaf_cls is None: return self leaf = leaf_cls() d = self._as_dict() leaf.__dict__.update(self._as_dict()) l = leaf._as_dict() return leaf @classmethod def get_type(cls): return re.sub(r'action$', '', cls.__name__.lower()) def save(self, data=None, threaded=True, *args, **kwargs): isnew = False if not self.id: self.action_type = self.__class__.get_type() isnew = True if data: self.process_data(**data) super(Action, self).save(*args, **kwargs) if isnew: if (self.node is None) or (not self.node.nis.wiki): self.repute_users() self.process_action() self.trigger_hooks(threaded, True) return self def delete(self, *args, **kwargs): self.cancel_action() super(Action, self).delete(*args, **kwargs) def cancel(self, user=None, ip=None): if not self.canceled: self.canceled = True self.canceled_at = datetime.datetime.now() self.canceled_by = (user is None) and self.user or user if ip: self.canceled_ip = ip self.save() self.cancel_reputes() self.cancel_action() #self.trigger_hooks(False) @classmethod def get_current(cls, **kwargs): kwargs['canceled'] = False try: return cls.objects.get(**kwargs) except cls.MultipleObjectsReturned: logging.error("Got multiple values for action %s with args %s", cls.__name__, ", ".join(["%s='%s'" % i for i in kwargs.items()])) raise except cls.DoesNotExist: return None @classmethod def hook(cls, fn): if not Action.hooks.get(cls, None): Action.hooks[cls] = [] Action.hooks[cls].append(fn) def trigger_hooks(self, threaded, new=True): from forum.models import CustomBadge CustomBadge.load_custom_badges() if threaded: thread = Thread(target=trigger_hooks, args=[self, Action.hooks, new]) thread.setDaemon(True) thread.start() else: trigger_hooks(self, Action.hooks, new) class Meta: app_label = 'forum'