Commits

Konrad Rymczak committed 60047c9

merge with github version + package_data

  • Participants
  • Parent commits 03ba1b8

Comments (0)

Files changed (21)

cmsplugin_news/admin.py

 
 from django.http import HttpResponse
 from django.core import serializers
+from django.core.exceptions import PermissionDenied
 
 from cmsplugin_news.forms import NewsForm
 from cmsplugin_news.models import News
-    
+
 class NewsAdmin(admin.ModelAdmin):
     """
         Admin for news
     """
     date_hierarchy = 'pub_date'
-    list_display = ('slug', 'title', 'is_published', 'pub_date')
+    list_display = ('slug', 'title', 'is_published', 'pub_date', 'author')
     #list_editable = ('title', 'is_published')
     list_filter = ('is_published', )
     search_fields = ['title', 'excerpt', 'content']
     prepopulated_fields = {'slug': ('title',)}
+    current_user_field = 'author' # will prepopulate this field when adding a new item
     form = NewsForm
-    
+
     actions = ['make_published', 'make_unpublished']
-    
+
     save_as = True
     save_on_top = True
-    
+
     def queryset(self, request):
         """
             Override to use the objects and not just the default visibles only.
         """
         return News.objects.all()
-       
+
+    def get_form(self, request, obj=None, **kwargs):
+        self.exclude = []
+        if not request.user.is_superuser:
+            self.exclude.append('is_published')
+        return super(NewsAdmin, self).get_form(request, obj, **kwargs)
+
+    def add_view(self, request, form_url='', extra_context=None):
+        # set the current user so that we can prepopulate the author field
+        self._current_user = request.user
+        return super(NewsAdmin, self).add_view(request, form_url, extra_context)
+
+    def save_model(self, request, obj, form, change):
+        if obj.is_published and not self.user_can_publish(request.user):
+            raise PermissionDenied('You do not have permission to save published news articles.')
+        else:
+            obj.save()
+
+    def formfield_for_dbfield (self, db_field, **kwargs):
+        field = super(NewsAdmin, self).formfield_for_dbfield(db_field, **kwargs)
+
+        # if we have a _current_user then preselect the currently logged in user
+        if hasattr(self, '_current_user') and db_field.name == self.current_user_field:
+            field.initial = self._current_user.pk
+        return  field
+
+    def user_can_publish(self, user):
+        """
+            Does the user have permission to publish/unpublish articles?
+        """
+        return user.has_perm('news.can_publish')
+
     def make_published(self, request, queryset):
         """
             Marks selected news items as published
         """
-        rows_updated = queryset.update(is_published=True)
-        self.message_user(request, ungettext('%(count)d newsitem was published', 
-                                            '%(count)d newsitems where published', 
-                                            rows_updated) % {'count': rows_updated})
+        if not self.user_can_publish(request.user):
+            raise PermissionDenied('You do not have permission to publish news articles.')
+        else:
+            rows_updated = queryset.update(is_published=True)
+            self.message_user(request, ungettext('%(count)d newsitem was published',
+                                                 '%(count)d newsitems where published',
+                                                 rows_updated) % {'count': rows_updated})
     make_published.short_description = _('Publish selected news')
 
     def make_unpublished(self, request, queryset):
         """
             Marks selected news items as unpublished
         """
-        rows_updated =queryset.update(is_published=False)
-        self.message_user(request, ungettext('%(count)d newsitem was unpublished', 
-                                            '%(count)d newsitems where unpublished', 
-                                            rows_updated) % {'count': rows_updated})
+        if not self.user_can_publish(request.user):
+            raise PermissionDenied('You do not have permission to unpublish news articles.')
+        else:
+            rows_updated =queryset.update(is_published=False)
+            self.message_user(request, ungettext('%(count)d newsitem was unpublished',
+                                                 '%(count)d newsitems where unpublished',
+                                                 rows_updated) % {'count': rows_updated})
     make_unpublished.short_description = _('Unpublish selected news')
 
 admin.site.register(News, NewsAdmin)

cmsplugin_news/cms_app.py

+from django.utils.translation import ugettext_lazy as _
+
+from cms.app_base import CMSApp
+from cms.apphook_pool import apphook_pool
+
+class NewsApphook(CMSApp):
+    name = _("Latest News")
+    urls = ["cmsplugin_news.urls"]
+
+apphook_pool.register(NewsApphook)

cmsplugin_news/cms_plugins.py

     """
     model = LatestNewsPlugin
     name = _('Latest news')
-    render_template = "cmsplugin_news/latest_news.html"
-    
-    
+    render_template = "cmsplugin_news/plugins/latest_news.html"
+
+
     def render(self, context, instance, placeholder):
         """
             Render the latest news
         return context
 
 if not settings.DISABLE_LATEST_NEWS_PLUGIN:
-    plugin_pool.register_plugin(CMSLatestNewsPlugin)
+    plugin_pool.register_plugin(CMSLatestNewsPlugin)

cmsplugin_news/forms.py

 class NewsForm(forms.ModelForm):
     class Meta:
         model = News
-        
+
     def _get_widget(self):
-        plugins = plugin_pool.get_text_enabled_plugins(placeholder=None)
+        plugins = plugin_pool.get_text_enabled_plugins(placeholder=None, page=None)
         if USE_TINYMCE and "tinymce" in settings.INSTALLED_APPS:
             from cmsplugin_news.widgets.tinymce_widget import TinyMCEEditor
             return TinyMCEEditor(installed_plugins=plugins)
         else:
             return WYMEditor(installed_plugins=plugins)
-        
-        
+
+
     def __init__(self, *args, **kwargs):
         super(NewsForm, self).__init__(*args, **kwargs)
         widget = self._get_widget()
         self.fields['content'].widget = widget
-        

cmsplugin_news/menu.py

+# -*- coding: utf-8 -*-
+
+from datetime import datetime
+from django.core.urlresolvers import reverse
+
+from menus.base import NavigationNode
+from menus.menu_pool import menu_pool
+from cms.menu_bases import CMSAttachMenu
+
+from django.utils.translation import ugettext_lazy as _
+
+from models import News
+
+
+class CMSLatestNewsAppMenu(CMSAttachMenu):
+
+    name = _("Latest News Application Navigation")
+
+    def get_nodes(self, request):
+        nodes = []
+        try:
+            years = months = days = slugs = []
+            for item in News.published.all():
+                pub_date = item.pub_date
+
+                if not pub_date.year in years:
+                    years.append(pub_date.year)
+                    nodes.append(NavigationNode(pub_date.year,
+                        reverse('news_archive_year', kwargs={'year': pub_date.year}), pub_date.year))
+                    months = []
+
+                if not pub_date.month in months:
+                    months.append(pub_date.month)
+                    nodes.append(NavigationNode(datetime.strftime(pub_date, '%B'),
+                        reverse('news_archive_month', kwargs={
+                            'year': pub_date.year,
+                            'month': datetime.strftime(pub_date, '%m'),
+                        }), datetime.strftime(pub_date, '%m'), pub_date.year))
+                    days = []
+
+                if not pub_date.day in days:
+                    days.append(pub_date.day)
+                    nodes.append(NavigationNode(datetime.strftime(pub_date, '%d'),
+                        reverse('news_archive_day', kwargs={
+                            'year': pub_date.year,
+                            'month': datetime.strftime(pub_date, '%m'),
+                            'day': datetime.strftime(pub_date, '%d'),
+                        }), datetime.strftime(pub_date, '%d'), datetime.strftime(pub_date, '%m')))
+                    slugs = []
+
+                if not item.slug in slugs:
+                    slugs.append(item.slug)
+                    nodes.append(NavigationNode(
+                        item.title, item.get_absolute_url(), item.pk, datetime.strftime(pub_date, '%d')))
+        except:
+            pass
+        return nodes
+
+menu_pool.register_menu(CMSLatestNewsAppMenu)

cmsplugin_news/migrations/0001_initial.py

 from cmsplugin_news.models import *
 
 class Migration:
-    
+
     def forwards(self, orm):
-        
+
         # Adding model 'News'
         db.create_table('cmsplugin_news_news', (
             ('id', orm['cmsplugin_news.News:id']),
             ('updated', orm['cmsplugin_news.News:updated']),
         ))
         db.send_create_signal('cmsplugin_news', ['News'])
-        
+
         # Adding model 'LatestNewsPlugin'
         db.create_table('cmsplugin_latestnewsplugin', (
             ('cmsplugin_ptr', orm['cmsplugin_news.LatestNewsPlugin:cmsplugin_ptr']),
             ('limit', orm['cmsplugin_news.LatestNewsPlugin:limit']),
         ))
         db.send_create_signal('cmsplugin_news', ['LatestNewsPlugin'])
-        
-    
-    
+
+
+
     def backwards(self, orm):
-        
+
         # Deleting model 'News'
         db.delete_table('cmsplugin_news_news')
-        
+
         # Deleting model 'LatestNewsPlugin'
         db.delete_table('cmsplugin_latestnewsplugin')
-        
-    
-    
+
+
+
     models = {
         'cms.cmsplugin': {
             'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
             'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
         }
     }
-    
+
     complete_apps = ['cmsplugin_news']

cmsplugin_news/migrations/0002_add_author_field.py

+# encoding: 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 field 'News.author'
+        db.add_column('cmsplugin_news_news', 'author', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True), keep_default=False)
+
+
+    def backwards(self, orm):
+        
+        # Deleting field 'News.author'
+        db.delete_column('cmsplugin_news_news', 'author_id')
+
+
+    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'})
+        },
+        'cms.cmsplugin': {
+            'Meta': {'object_name': 'CMSPlugin'},
+            'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'language': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}),
+            'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.CMSPlugin']", 'null': 'True', 'blank': 'True'}),
+            'placeholder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.Placeholder']", 'null': 'True'}),
+            'plugin_type': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
+            'position': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'})
+        },
+        'cms.placeholder': {
+            'Meta': {'object_name': 'Placeholder'},
+            'default_width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'slot': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'})
+        },
+        'cmsplugin_news.latestnewsplugin': {
+            'Meta': {'object_name': 'LatestNewsPlugin', 'db_table': "'cmsplugin_latestnewsplugin'", '_ormbases': ['cms.CMSPlugin']},
+            'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'}),
+            'limit': ('django.db.models.fields.PositiveIntegerField', [], {})
+        },
+        'cmsplugin_news.news': {
+            'Meta': {'ordering': "('-pub_date',)", 'object_name': 'News'},
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+            'content': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'excerpt': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_published': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'pub_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2011, 2, 25, 11, 5, 53, 684784)'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
+        },
+        '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'})
+        }
+    }
+
+    complete_apps = ['cmsplugin_news']

cmsplugin_news/migrations/0003_add_publishing_permission.py

+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    permission_codename = 'can_publish'
+    permission_name = 'Can publish/unpublish News articles'
+
+    def get_or_create_content_type(self, orm):
+        ContentType = orm['contenttypes.ContentType']
+        ct, created = ContentType.objects.get_or_create(model=u'news', app_label=u'cmsplugin_news', name='News')
+        return ct
+
+    def forwards(self, orm):
+        Permission = orm['auth.Permission']
+        ct = self.get_or_create_content_type(orm)
+        permission, created = Permission.objects.get_or_create(content_type=ct, codename=self.permission_codename, name=self.permission_name)
+
+    def backwards(self, orm):
+        Permission = orm['auth.Permission']
+        ct = self.get_or_create_content_type(orm)
+        Permission.objects.get(content_type=ct, codename=self.permission_codename, name=self.permission_name).delete()
+
+    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'})
+            },
+        'cms.cmsplugin': {
+            'Meta': {'object_name': 'CMSPlugin'},
+            'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'language': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}),
+            'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.CMSPlugin']", 'null': 'True', 'blank': 'True'}),
+            'placeholder': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.Placeholder']", 'null': 'True'}),
+            'plugin_type': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
+            'position': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+            'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'})
+            },
+        'cms.placeholder': {
+            'Meta': {'object_name': 'Placeholder'},
+            'default_width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'slot': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'})
+            },
+        'cmsplugin_news.latestnewsplugin': {
+            'Meta': {'object_name': 'LatestNewsPlugin', 'db_table': "'cmsplugin_latestnewsplugin'", '_ormbases': ['cms.CMSPlugin']},
+            'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'}),
+            'limit': ('django.db.models.fields.PositiveIntegerField', [], {})
+            },
+        'cmsplugin_news.news': {
+            'Meta': {'ordering': "('-pub_date',)", 'object_name': 'News'},
+            'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
+            'content': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'excerpt': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_published': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'pub_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2011, 2, 25, 13, 57, 19, 206434)'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
+            },
+        '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'})
+        }
+    }
+
+    complete_apps = ['cmsplugin_news']

cmsplugin_news/models.py

 import datetime
 
 from django.db import models
+from django.contrib.auth.models import User
 from django.utils.translation import ugettext_lazy as _
 
 from cms.models import CMSPlugin
 
+
 class PublishedNewsManager(models.Manager):
     """
         Filters out all unpublished and items with a publication date in the future
         return super(PublishedNewsManager, self).get_query_set() \
                     .filter(is_published=True) \
                     .filter(pub_date__lte=datetime.datetime.now())
-    
+
 class News(models.Model):
     """
     News
     """
-    
+
     title           = models.CharField(_('Title'), max_length=255)
-    slug            = models.SlugField(_('Slug'), unique_for_date='pub_date', 
+    slug            = models.SlugField(_('Slug'), unique_for_date='pub_date',
                         help_text=_('A slug is a short name which uniquely identifies the news item for this day'))
+    author          = models.ForeignKey(User, blank=True, null=True)
+
     excerpt         = models.TextField(_('Excerpt'), blank=True)
     content         = models.TextField(_('Content'), blank=True)
-    
+
     is_published    = models.BooleanField(_('Published'), default=False)
     pub_date        = models.DateTimeField(_('Publication date'), default=datetime.datetime.now())
-    
+
     created         = models.DateTimeField(auto_now_add=True, editable=False)
     updated         = models.DateTimeField(auto_now=True, editable=False)
-    
+
     published = PublishedNewsManager()
     objects = models.Manager()
-    
+
     class Meta:
         verbose_name = _('News')
         verbose_name_plural = _('News')
         ordering = ('-pub_date', )
-    
+        permissions = (
+            ('can_publish', 'Can publish/unpublish news articles'),
+        )
+
     def __unicode__(self):
         return self.title
-    
+
     @models.permalink
     def get_absolute_url(self):
         return ('news_detail', (), { 'year': self.pub_date.strftime("%Y"),
                                      'month': self.pub_date.strftime("%m"),
                                      'day': self.pub_date.strftime("%d"),
                                      'slug': self.slug })
-    
+
 class LatestNewsPlugin(CMSPlugin):
     """
         Model for the settings when using the latest news cms plugin
     """
-    limit = models.PositiveIntegerField(_('Number of news items to show'), 
+    limit = models.PositiveIntegerField(_('Number of news items to show'),
                     help_text=_('Limits the number of items that will be displayed'))

cmsplugin_news/templates/cmsplugin_news/news_archive.html

 <h1>Latest news</h1>
 
 {% for object in latest %}
-	<p><strong>{{ object.title }}</strong><br/><a href="{{ object.get_absolute_url }}">Read more</a></p>
-{% endfor %}
+    <p><strong>{{ object.title }}</strong><br/><a href="{{ object.get_absolute_url }}">Read more</a></p>
+{% endfor %}

cmsplugin_news/templates/cmsplugin_news/news_archive_day.html

 <h1>News for {{ day }}</h1>
 
 <ul>
-	{% for object in object_list %}
-		<li><a href="{{ object.get_absolute_url }}">{{ object.title }}</a></li>
-	{% empty %}
-		<li>No news for this day</li>
-	{% endfor %}
-</ul>
+    {% for object in object_list %}
+        <li><a href="{{ object.get_absolute_url }}">{{ object.title }}</a></li>
+    {% empty %}
+        <li>No news for this day</li>
+    {% endfor %}
+</ul>

cmsplugin_news/templates/cmsplugin_news/news_archive_month.html

 <h1>News for {{ month }}</h1>
 
 <ul>
-	{% for object in object_list %}
-		<li><a href="{{ object.get_absolute_url }}">{{ object.title }}</a></li>
-	{% empty %}
-		<li>No news for this month</li>
-	{% endfor %}
-</ul>
+    {% for object in object_list %}
+        <li><a href="{{ object.get_absolute_url }}">{{ object.title }}</a></li>
+    {% empty %}
+        <li>No news for this month</li>
+    {% endfor %}
+</ul>

cmsplugin_news/templates/cmsplugin_news/news_archive_year.html

     {% empty %}
         <li>No news for this year</li>
     {% endfor %}
-</ul>
+</ul>

cmsplugin_news/templates/cmsplugin_news/news_detail.html

 <h1>{{ object.title }}</h1>
 
-<p><span class="date">{{ object.pub_date }}</span><br/>
-	{{ object.content|safe }}
+<p>
+    <span class="date">{{ object.pub_date }}</span><br/>
+{% if object.author %}
+    <span class="author">{{ object.author.get_full_name }}</span><br/>
+{% endif %}
+    {{ object.content|safe }}
 </p>
-

cmsplugin_news/templates/cmsplugin_news/plugins/latest_news.html

+{% load i18n %}
+
+<h1>{% trans "Latest news" %}</h1>
+<ul>
+{% for news in latest %}
+    <li><a href="{{ news.get_absolute_url }}">{{ news.title }}</a></li>
+{% empty %}
+    <li>{% trans "No news yet" %}</li>
+{% endfor %}
+</ul>
+<a href="{% url news_archive_index %}">Archive</a>

cmsplugin_news/templates/cmsplugin_news/widgets/wymeditor.html

         skinPath: "{{ CMS_MEDIA_URL }}js/wymeditor/skins/django/",
         updateSelector: 'input[type=submit],',
         updateEvent: 'click',
-		logoHtml: '',
-		toolsItems: [
-			    {{ WYM_TOOLS }}
-			],
-		containersItems: [
-		        {{ WYM_CONTAINERS }}
-		    ],
-		classesItems: [
-			    {{ WYM_CLASSES }}
-			],
-		editorStyles: [
-			{{ WYM_STYLES }}
-			],
-		{% if WYM_STYLESHEET %}
-		stylesheet:
-			{{ WYM_STYLESHEET }}
-		,
-		{% endif %}
+                logoHtml: '',
+                toolsItems: [
+                            {{ WYM_TOOLS }}
+                        ],
+                containersItems: [
+                        {{ WYM_CONTAINERS }}
+                    ],
+                classesItems: [
+                            {{ WYM_CLASSES }}
+                        ],
+                editorStyles: [
+                        {{ WYM_STYLES }}
+                        ],
+                {% if WYM_STYLESHEET %}
+                stylesheet:
+                        {{ WYM_STYLESHEET }}
+                ,
+                {% endif %}
         //handle click event on dialog's submit button
         postInitDialog: function( wym, wdw ) {
-     
+
         }
     });
 });

cmsplugin_news/tests.py

 import datetime
 
 from django.test import TestCase
+from django.contrib.auth.models import Permission, User
 
-from cmsplugin_news.models import News
-from cmsplugin_news.navigation import get_nodes
+from models import News
 
 class NewsTest(TestCase):
     def setUp(self):
         self.today = datetime.datetime.today()
         self.yesterday = self.today - datetime.timedelta(days=1)
         self.tomorrow = self.today + datetime.timedelta(days=1)
-        
+
     def tearDown(self):
         pass
 
-        
+
     def test_unpublished(self):
         """
             Test if unpublished items are hidden by default
         unpublished.is_published = False
         unpublished.save()
         self.assertEquals(News.published.count(), 0)
-        
+
         unpublished.delete()
-        
+
     def test_future_published(self):
         """
             Tests that items with a future published date are hidden
         future_published.pub_date = self.tomorrow
         future_published.save()
         self.assertEquals(News.published.count(), 0)
-        
-    def test_navigation(self):
+
+    def test_publish_permissions(self):
         """
-            Tests if the navigation build by navigation.get_nodes is correct
+            Tests assigning of can_publish permission for superuser/non superuser users
         """
-        pass
+        superuser = User.objects.create(username='superman', is_staff=True, is_superuser=True)
+        averagepleb = User.objects.create(username='pleb', is_staff=True, is_superuser=False)
+
+        permission = 'news.can_publish'
+
+        self.assertTrue(superuser.has_perm(permission))
+        self.assertFalse(averagepleb.has_perm(permission))

cmsplugin_news/urls.py

 }
 
 urlpatterns = patterns('django.views.generic.date_based',
-    (r'^$', 
+    (r'^$',
         'archive_index', news_info_dict, 'news_archive_index'),
-    
-    (r'^(?P<year>\d{4})/$', 
+
+    (r'^(?P<year>\d{4})/$',
         'archive_year', news_info_dict, 'news_archive_year'),
-    
-    (r'^(?P<year>\d{4})/(?P<month>\d{2})/$', 
+
+    (r'^(?P<year>\d{4})/(?P<month>\d{2})/$',
         'archive_month', news_info_month_dict, 'news_archive_month'),
-    
-    (r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/$', 
+
+    (r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/$',
         'archive_day', news_info_month_dict, 'news_archive_day'),
-    
-    (r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<slug>[-\w]+)/$', 
+
+    (r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<slug>[-\w]+)/$',
         'object_detail', news_info_month_dict, 'news_detail'),
 )
-

cmsplugin_news/widgets/tinymce_widget.py

 import cms.plugins.text.settings
 
 class TinyMCEEditor(TinyMCE):
-    
+
     def __init__(self, installed_plugins=None,  **kwargs):
         super(TinyMCEEditor, self).__init__(**kwargs)
         self.installed_plugins = installed_plugins
-        
+
     def render_additions(self, name, value, attrs=None):
         language = get_language()
         context = {
         }
         return mark_safe(render_to_string(
             'cmsplugin_news/widgets/tinymce.html', context))
-        
+
     def _media(self):
         media = super(TinyMCEEditor, self)._media()
         media.add_js([join(settings.CMS_MEDIA_URL, path) for path in (
                       )])
         media.add_css({"all":[join(settings.CMS_MEDIA_URL, path) for path in ('css/jquery/cupertino/jquery-ui.css',
                                                                      'css/tinymce_toolbar.css')]})
-        
+
         return media
-    
-    
+
+
     media = property(_media)
-    
+
     def render(self, name, value, attrs=None):
         if value is None: value = ''
         value = smart_unicode(value)
         mce_config['theme_advanced_buttons1'] = adv2
         """
         json = simplejson.dumps(mce_config)
-        
+
         html = [u'<textarea%s>%s</textarea>' % (flatatt(final_attrs), escape(value))]
         if tinymce.settings.USE_COMPRESSOR:
             compressor_config = {
             html.append(u'<script type="text/javascript">tinyMCE_GZ.init(%s);</script>' % (c_json))
         html.append(u'<script type="text/javascript">%s;\ntinyMCE.init(%s);</script>' % (self.render_additions(name, value, attrs), json))
         return mark_safe(u'\n'.join(html))
-    
-    
-    

cmsplugin_news/widgets/wymeditor_widget.py

             'wymeditor/jquery.wymeditor.js',
             'wymeditor/plugins/resizable/jquery.wymeditor.resizable.js',
         )]
-              
+
     def __init__(self, attrs=None, installed_plugins=None):
         """
         Create a widget for editing text + plugins.
             self.attrs.update(attrs)
         super(WYMEditor, self).__init__(attrs)
         self.installed_plugins = installed_plugins
-        
+
     def render_textarea(self, name, value, attrs=None):
         return super(WYMEditor, self).render(name, value, attrs)
 
 
     def render(self, name, value, attrs=None):
         return self.render_textarea(name, value, attrs) + \
-            self.render_additions(name, value, attrs)
+            self.render_additions(name, value, attrs)
 
 setup(
     name='cmsplugin-news',
-    version='0.3b',
+    version='0.4b',
     description='This is a news app/plugin for the django-cms 2',
     author='Harro van der Klauw',
     author_email='hvdklauw@gmail.com',
-    url='http://bitbucket.org/MrOxiMoron/cmsplugin-news/',
+    url='https://bitbucket.org/krymczak/cmsplugin-news',
     packages=find_packages(),
     classifiers=[
         'Development Status :: 4 - Beta',
     ],
     include_package_data=True,
     zip_safe=False,
+    package_data = {
+        'cmsplugin_news': [
+            'templates/cmsplugin_news/*.html',
+        ],
+    },
     install_requires=['setuptools', 'setuptools_bzr'],
 )