Commits

Jakub Zalewski committed 880623f

pluggable

Comments (0)

Files changed (17)

+include README.rst
+PyBBm Extensions
+=================
+
+Extensions for PyBBm forum.
+
+Docs
+-----
+
+* https://pybbm-extensions.rtfd.org/
+
+Sauce
+------
+
+* http://pypi.python.org/pypi/pybbm-extensions/
+* https://bitbucket.org/zalew/pybbm-extensions/
+ 

pybb_extensions/__init__.py

+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+VERSION = (0, 1, 0)
+DEV_STATUS = '3 - Alpha'

pybb_extensions/models.py

+#!/usr/bin/python
+# -*- coding: utf-8 -*-

pybb_extensions/pluggable/__init__.py

Empty file added.

pybb_extensions/pluggable/forms.py

+#!/usr/bin/python
+# -*- coding: utf-8 -*-

pybb_extensions/pluggable/managers.py

+#!/usr/bin/python
+# -*- coding: utf-8 -*-

pybb_extensions/pluggable/migrations/0001_initial.py

+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        # Adding model 'Plug'
+        db.create_table('pybb_ext_plug', (
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('topic', self.gf('django.db.models.fields.related.OneToOneField')(related_name='plug', unique=True, to=orm['pybb.Topic'])),
+            ('content_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['contenttypes.ContentType'])),
+            ('object_id', self.gf('django.db.models.fields.IntegerField')()),
+        ))
+        db.send_create_signal('pluggable', ['Plug'])
+
+        # Adding unique constraint on 'Plug', fields ['content_type', 'object_id']
+        db.create_unique('pybb_ext_plug', ['content_type_id', 'object_id'])
+
+
+    def backwards(self, orm):
+        # Removing unique constraint on 'Plug', fields ['content_type', 'object_id']
+        db.delete_unique('pybb_ext_plug', ['content_type_id', 'object_id'])
+
+        # Deleting model 'Plug'
+        db.delete_table('pybb_ext_plug')
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'pluggable.plug': {
+            'Meta': {'unique_together': "(('content_type', 'object_id'),)", 'object_name': 'Plug', 'db_table': "'pybb_ext_plug'"},
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'object_id': ('django.db.models.fields.IntegerField', [], {}),
+            'topic': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'plug'", 'unique': 'True', 'to': "orm['pybb.Topic']"})
+        },
+        'pybb.category': {
+            'Meta': {'ordering': "['position']", 'object_name': 'Category'},
+            'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
+            'position': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'}),
+            'registered': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+        },
+        'pybb.forum': {
+            'Meta': {'ordering': "['position']", 'object_name': 'Forum'},
+            'category': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'forums'", 'to': "orm['pybb.Category']"}),
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'headline': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'moderators': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
+            'position': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'}),
+            'post_count': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'}),
+            'readed_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'readed_forums'", 'symmetrical': 'False', 'through': "orm['pybb.ForumReadTracker']", 'to': "orm['auth.User']"}),
+            'registered': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'topic_count': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'pybb.forumreadtracker': {
+            'Meta': {'object_name': 'ForumReadTracker'},
+            'forum': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['pybb.Forum']", 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'time_stamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+        },
+        'pybb.topic': {
+            'Meta': {'ordering': "['-created']", 'object_name': 'Topic'},
+            'closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'forum': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'topics'", 'to': "orm['pybb.Forum']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'on_moderation': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'poll_question': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'poll_type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'post_count': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'}),
+            'readed_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'readed_topics'", 'symmetrical': 'False', 'through': "orm['pybb.TopicReadTracker']", 'to': "orm['auth.User']"}),
+            'sticky': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'subscribers': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'subscriptions'", 'blank': 'True', 'to': "orm['auth.User']"}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+            'views': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True'})
+        },
+        'pybb.topicreadtracker': {
+            'Meta': {'object_name': 'TopicReadTracker'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'time_stamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'topic': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['pybb.Topic']", 'null': 'True', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
+        },
+        'taggit.tag': {
+            'Meta': {'object_name': 'Tag'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'})
+        },
+        'taggit.taggeditem': {
+            'Meta': {'object_name': 'TaggedItem'},
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_tagged_items'", 'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+            'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_items'", 'to': "orm['taggit.Tag']"})
+        }
+    }
+
+    complete_apps = ['pluggable']

pybb_extensions/pluggable/migrations/__init__.py

Empty file added.

pybb_extensions/pluggable/models.py

+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+from django.contrib.auth.models import User
+from django.contrib.contenttypes import generic
+from django.contrib.contenttypes.models import ContentType
+from django.db import models
+from django.db.models.manager import Manager
+from django.db.models.signals import post_save
+from pybb.models import Topic, Post
+from pybb_extensions.pluggable.utils import get_datetime_now
+from django.utils.html import strip_tags
+
+registry = []
+
+
+class PlugManager(Manager):
+
+    def _default_qs(self):
+        return super(PlugManager, self).get_query_set()
+
+    def get_for_object(self, obj):
+        ctype = ContentType.objects.get_for_model(obj.__class__)
+        try:
+            return self._default_qs().get(content_type=ctype, object_id=obj.id)
+        except:
+            return None
+
+    def create_for_object(self, obj, forum_id, topic=None, user=None, body=None):
+        try:
+            name = obj.forum_topic_title()
+        except:
+            name = str(obj)
+
+        try:
+            added = obj.forum_topic_added()
+        except:
+            added = get_datetime_now()
+
+        try:
+            user = obj.forum_topic_user()
+        except:
+            try:
+                user = obj.user
+            except:
+                user = User.objects.filter(is_superuser=True).order_by('id')[0].get()
+
+        if not body:
+            try:
+                body = obj.forum_topic_body()
+            except:
+                body = str(obj)
+
+        if not topic:
+            topic = Topic.objects.create(forum_id=forum_id, user=user, name=name)
+            topic.created = added
+            topic.save()
+        first_post, created = Post.objects.get_or_create(topic_id=topic.id, user=user, body=body, body_html=body, body_text=strip_tags(body), created=added)
+
+        ctype = ContentType.objects.get_for_model(obj.__class__)
+        return super(PlugManager, self).create(topic_id=topic.id, content_type=ctype, object_id=obj.id)
+
+
+class Plug(models.Model):
+    topic = models.OneToOneField(Topic, related_name='plug')
+    content_type = models.ForeignKey(ContentType)
+    object_id = models.IntegerField()
+    content_object = generic.GenericForeignKey('content_type', 'object_id')
+
+    objects = PlugManager()
+
+    class Meta:
+        db_table = 'pybb_ext_plug'
+        unique_together = ('content_type', 'object_id')
+
+    def content_url(self):
+        try:
+            return self.content_object.get_absolute_url()
+        except:
+            return None
+
+    def posts(self):
+        posts = Post.objects.filter(topic=self.topic)
+        return posts
+
+    @classmethod
+    def create_topic(cls):
+        pass
+
+
+class AlreadyRegistered(Exception):
+    pass
+
+
+def register(model, attr="plug"):
+    if model in registry:
+        raise AlreadyRegistered(('The model %s has already been registered.') % model.__name__)
+    if not hasattr(model, 'forum_id'):
+        raise Exception('You must specify a forum_id() method in model %s.' % model.__name__)
+    registry.append(model)
+
+    def get_plug_for_instance(self):
+        return Plug.objects.get_for_object(self)
+
+    model.add_to_class('plug', get_plug_for_instance)
+    post_save.connect(plug_signal_handler, sender=model)
+
+
+def plug_signal_handler(sender, instance, **kwargs):
+    if kwargs.get('created', False):
+        Plug.objects.create_for_object(instance, instance.forum_id())

pybb_extensions/pluggable/templatetags/__init__.py

Empty file added.

pybb_extensions/pluggable/templatetags/pybb_pluggable_tags.py

+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+from django import template
+from pybb.forms import PostForm, AdminPostForm
+from pybb.models import Post
+from pybb_extensions.pluggable.models import Plug, registry
+register = template.Library()
+
+
+@register.inclusion_tag("pybb_extensions/pluggable/topic.html")
+def pluggable_topic(obj, user=None):
+    plug = Plug.objects.get_for_object(obj)
+    if plug:
+        return {'posts': plug.posts, 'topic': plug.topic, 'user': user, }
+    return {'user': user, }
+
+
+@register.inclusion_tag("pybb_extensions/pluggable/form.html")
+def pluggable_topic_form(obj, user=None):
+    plug = Plug.objects.get_for_object(obj)
+    if plug:
+        if user.is_staff:
+            form = AdminPostForm(initial={'login': user.username}, topic=plug.topic)
+        else:
+            form = PostForm(topic=plug.topic)
+        return {'plug': plug, 'topic': plug.topic, 'form': form, 'user': user, }
+    return {'user': user, }

pybb_extensions/pluggable/urls.py

+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+from pybb_extensions.pluggable.views import AddPostView
+
+try:
+    from django.conf.urls import url, patterns
+except ImportError:
+    from django.conf.urls.defaults import url, patterns
+
+
+urlpatterns = patterns('pybb_extensions.pluggable.views',
+                       url('^(?P<plug_id>\d+)/post/dodaj/$', AddPostView.as_view(), name='add_post'),
+                       )

pybb_extensions/pluggable/utils.py

+#!/usr/bin/env python
+# encoding: utf-8
+import datetime
+
+
+def get_datetime_now():
+    try:
+        from django.utils import timezone
+        return timezone.now()
+    except ImportError:
+        return datetime.datetime.now()

pybb_extensions/pluggable/views.py

+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+from django.views import generic
+from pybb.views import PostEditMixin
+from django.core.urlresolvers import reverse
+from pybb import defaults
+
+
+class AddPostView(PostEditMixin, generic.CreateView):
+
+    def get_success_url(self):
+        if (not self.request.user.is_authenticated()) and defaults.PYBB_PREMODERATION:
+            return reverse('pybb:index')
+        return super(AddPostView, self).get_success_url()

pybb_extensions/tests.py

+#!/usr/bin/env python
+# encoding: utf-8
+from django.contrib.auth.models import User
+from django.db import models
+from django.test import TestCase
+from django.test.client import Client
+from pybb.models import Forum, Category, Topic
+from pybb_extensions.pluggable.models import register, AlreadyRegistered, Plug, \
+    registry
+from pybb_extensions.pluggable.utils import get_datetime_now
+
+
+class Bogus(models.Model):
+    title = models.CharField(max_length=3)
+    user = models.ForeignKey(User)
+    added = models.DateTimeField(default=get_datetime_now())
+
+    def __unicode__(self):
+        return self.title
+
+    def forum_id(self):
+        return 1
+
+    def forum_topic_title(self):
+        return self.title
+
+    def forum_topic_body(self):
+        return 'yo'
+
+    def forum_topic_added(self):
+        return self.added
+
+
+class BogusTwo(models.Model):
+    title = models.CharField(max_length=3)
+    user = models.ForeignKey(User)
+    added = models.DateTimeField(default=get_datetime_now())
+
+    def forum_id(self):
+        return 1
+
+
+class BBPlugTests(TestCase):
+
+    def setUp(self):
+        self.client = Client(enforce_csrf_checks=False)
+        user, created = User.objects.get_or_create(username='zalew')
+        user.set_password('test')
+        user.save()
+        self.user = user
+        Forum.objects.create(id=1, category=Category.objects.create(id=1))
+
+    def test_plug(self):
+        register(Bogus)
+        with self.assertRaises(AlreadyRegistered) as e:
+            register(Bogus)
+
+        self.assertTrue(hasattr(Bogus, 'plug'))
+
+        b, cr = Bogus.objects.get_or_create(id=1, user=self.user, title='elo elo')
+        self.assertTrue(hasattr(b, 'plug'))
+        self.assertTrue(isinstance(b.plug(), Plug))
+        self.assertEqual(b.plug().topic.forum.id, 1)
+        self.assertEqual(b.plug().topic.name, 'elo elo')
+        self.assertEqual(b.plug().topic.head.body, 'yo')
+        self.assertEqual(b.plug().topic.user, b.user)
+        self.assertEqual(b.plug().topic.created, b.added)
+
+    def test_plug_topic(self):
+
+        b, cr = BogusTwo.objects.get_or_create(id=2, user=self.user, title='siema nara')
+        topic = Topic.objects.create(forum_id=1, name=b.title, user=b.user)
+        topic.created = b.added
+        topic.updated = b.added
+        topic.save()
+
+        self.assertEqual(Plug.objects.get_for_object(b), None)
+
+        plug = Plug.objects.create_for_object(b, 1, topic=topic, body='hello')
+
+        self.assertEqual(plug.topic.forum.id, 1)
+        self.assertEqual(plug.topic.name, 'siema nara')
+        self.assertEqual(plug.topic.user, b.user)
+        self.assertEqual(plug.topic.created, b.added)
+        self.assertEqual(plug.topic.head.body, 'hello')
+
+        self.assertFalse(hasattr(b, 'plug'))
+        register(BogusTwo)
+        self.assertTrue(hasattr(b, 'plug'))
+        self.assertTrue(isinstance(b.plug(), Plug))
+
+        self.assertEqual(Plug.objects.get_for_object(b), plug)
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+import os
+from setuptools import setup, find_packages
+from pybb_extensions import VERSION, DEV_STATUS
+
+setup(
+    name='pybbm-extensions',
+    version='.'.join(map(str, VERSION)),
+    description='Extensions for PyBBm forum.',
+    long_description=open(os.path.join(os.path.dirname(__file__), 'README.rst')).read(),
+    keywords='pybbm extensions',
+    author='Jakub Zalewski',
+    author_email='zalew7@gmail.com',
+    url='https://bitbucket.org/zalew/pybbm-extensions',
+    license='public domain',
+    packages=find_packages(),
+    zip_safe=False,
+    package_data={
+        'readonly': [],
+    },
+    classifiers=[
+        'Development Status :: %s' % DEV_STATUS,
+        'Environment :: Web Environment',
+        'Intended Audience :: Developers',
+        'License :: Public Domain',
+        'Operating System :: OS Independent',
+        'Programming Language :: Python',
+        'Framework :: Django',
+    ],
+    install_requires=[
+        'django',
+        'pybbm',
+    ],
+)