diff --git a/news/admin.py b/news/admin.py index 2d5777a2aea2ac99d90e58d872fc73833d3a516b..c46a0edb5ae5e22f5dcd59dc72d29c635814d3d7 100644 --- a/news/admin.py +++ b/news/admin.py @@ -4,7 +4,18 @@ __license__ = "AGPL v3" from django.contrib import admin -from .models import NewsItem +from .models import NewsLetter, NewsItem, NewsLetterNewsItemsTable + + +class NewsLetterNewsItemsTableInline(admin.TabularInline): + model = NewsLetterNewsItemsTable + +class NewsLetterAdmin(admin.ModelAdmin): + search_fields = ['intro', 'closing'] + list_display = ['__str__', 'published'] + inlines = [NewsLetterNewsItemsTableInline] + +admin.site.register(NewsLetter, NewsLetterAdmin) class NewsItemAdmin(admin.ModelAdmin): diff --git a/news/forms.py b/news/forms.py new file mode 100644 index 0000000000000000000000000000000000000000..843a31a652217ae7a9d7d87e61a1f862f681a0ff --- /dev/null +++ b/news/forms.py @@ -0,0 +1,31 @@ +__copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" +__license__ = "AGPL v3" + + +from django import forms + +from .models import NewsLetter, NewsItem, NewsLetterNewsItemsTable + + +class NewsLetterForm(forms.ModelForm): + + class Meta: + model = NewsLetter + fields = ['date', 'intro', 'closing', 'published'] + + +class NewsItemForm(forms.ModelForm): + + class Meta: + model = NewsItem + fields = ['date', 'headline', 'blurb_short', 'blurb', + 'image', 'css_class', + 'followup_link', 'followup_link_text', + 'published', 'on_homepage'] + + +class NewsLetterNewsItemsTableForm(forms.ModelForm): + + class Meta: + model = NewsLetterNewsItemsTable + fields = ['newsitem'] diff --git a/news/migrations/0002_auto_20180706_1459.py b/news/migrations/0002_auto_20180706_1459.py new file mode 100644 index 0000000000000000000000000000000000000000..d06e2f8426df6d1c31288f15a2fb44c964ed647a --- /dev/null +++ b/news/migrations/0002_auto_20180706_1459.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2018-07-06 12:59 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='NewsLetter', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date', models.DateField()), + ('intro', models.TextField()), + ('closing', models.TextField()), + ('published', models.BooleanField(default=False)), + ], + ), + migrations.CreateModel( + name='NewsLetterNewsItemsTable', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('order', models.PositiveSmallIntegerField()), + ], + ), + migrations.AddField( + model_name='newsitem', + name='blurb_short', + field=models.TextField(default='', help_text='Short version for use in Newsletter/emails etc'), + ), + migrations.AddField( + model_name='newsitem', + name='css_class', + field=models.CharField(blank=True, max_length=256, verbose_name='Additional image CSS class'), + ), + migrations.AddField( + model_name='newsitem', + name='image', + field=models.ImageField(blank=True, upload_to='news/newsitems/%Y/'), + ), + migrations.AddField( + model_name='newsitem', + name='published', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='newsletternewsitemstable', + name='newsitem', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='news.NewsItem'), + ), + migrations.AddField( + model_name='newsletternewsitemstable', + name='newsletter', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='news.NewsLetter'), + ), + ] diff --git a/news/models.py b/news/models.py index 3bfdbdbd4c57fbd38c12e8bdd4d4b39512b29519..2e151fde38470a0d1c1b4002d427c5cc7035e9f5 100644 --- a/news/models.py +++ b/news/models.py @@ -8,12 +8,39 @@ from django.db import models from .managers import NewsManager +class NewsLetter(models.Model): + """ + Container of NewsItems. + Which NewsItems (and their order) are handled via the auxiliary + model NewsLetterNewsItemsTable. + """ + date = models.DateField() + intro = models.TextField() + closing = models.TextField() + published = models.BooleanField(default=False) + + def __str__(self): + return 'SciPost Newsletter %s' % self.date.strftime('%Y-%m-%d') + + def get_absolute_url(self): + return reverse('news:newsletter_detail', + kwargs={'year': self.date.strftime('%Y'), + 'month': self.date.strftime('%m'), + 'day': self.date.strftime('%d')}) + + class NewsItem(models.Model): date = models.DateField() headline = models.CharField(max_length=300) + blurb_short = models.TextField(default='', + help_text='Short version for use in Newsletter/emails etc') blurb = models.TextField() + image = models.ImageField(upload_to='news/newsitems/%Y/', blank=True) + css_class = models.CharField(max_length=256, blank=True, + verbose_name='Additional image CSS class') followup_link = models.URLField(blank=True) followup_link_text = models.CharField(max_length=300, blank=True) + published = models.BooleanField(default=False) on_homepage = models.BooleanField(default=True) objects = NewsManager() @@ -27,3 +54,13 @@ class NewsItem(models.Model): def get_absolute_url(self): return reverse('news:news') + '#news_' + str(self.id) + + +class NewsLetterNewsItemsTable(models.Model): + """ + Carries the specification of which NewsItem sits in which NewsLetter, + and in which order. + """ + newsletter = models.ForeignKey('news.NewsLetter', on_delete=models.CASCADE) + newsitem = models.ForeignKey('news.NewsItem', on_delete=models.CASCADE) + order = models.PositiveSmallIntegerField() diff --git a/news/templates/news/_newsletter_contents.html b/news/templates/news/_newsletter_contents.html new file mode 100644 index 0000000000000000000000000000000000000000..452b4e972a8d9dcbe532ec8223d09d81b6103242 --- /dev/null +++ b/news/templates/news/_newsletter_contents.html @@ -0,0 +1,16 @@ +<div class="row"> + <div class="col-12"> + <div class="card card-grey card-news"> + <h1>SciPost Newsletter {{ nl.date|date:'Y-m-d' }}</h1> + <p>{{ nl.intro|safe }}</p> + </div> + {% for nt in nl.newsletternewsitemstable_set.all|dictsort:'order' %} + <div class="card card-grey card-news"> + {% include 'news/news_card_content.html' with news=nt.newsitem %} + </div> + {% endfor %} + <div class="card card-grey card-news"> + <p>{{ nl.closing|safe }}</p> + </div> + </div> +</div> diff --git a/news/templates/news/news_card_content.html b/news/templates/news/news_card_content.html index 134db502bedc715be0da60461a79a7e45e946310..99c6e78098a599e7c31fc4d6b47ae234dfb550e6 100644 --- a/news/templates/news/news_card_content.html +++ b/news/templates/news/news_card_content.html @@ -1,11 +1,22 @@ <div class="card-body news-item" id="news_{{news.id}}"> - <h2 class="card-title">{{news.headline}}</h2> - <div> - <div class="text-muted date">{{news.date|date:'j F Y'}}</div> - <div class="pb-3">{{news.blurb|safe}}</div> + {% if news.image %} + <div class="row"> - {% if news.followup_link %} - <a href="{{news.followup_link}}">{{news.followup_link_text}}</a> - {% endif %} + <div class="col-3"> + <img class="d-flex mr-3 {{ news.image.css_class }}" src="{{ news.image.url }}" alt="image"/> </div> + <div class="col-9"> + {% endif %} + <div> + <h2 class="card-title">{{news.headline}}</h2> + <div class="text-muted date">{{news.date|date:'j F Y'}}</div> + <div class="pb-3">{{news.blurb|safe}}</div> + {% if news.followup_link %} + <a href="{{news.followup_link}}">{{news.followup_link_text}}</a> + {% endif %} + </div> + {% if news.image %} + </div> + </div> + {% endif %} </div> diff --git a/news/templates/news/news_manage.html b/news/templates/news/news_manage.html new file mode 100644 index 0000000000000000000000000000000000000000..282fd278e857a644b5356517a008142624d67b86 --- /dev/null +++ b/news/templates/news/news_manage.html @@ -0,0 +1,49 @@ +{% extends 'scipost/base.html' %} + +{% load bootstrap %} + +{% block pagetitle %}: News Management{% endblock pagetitle %} + +{% block content %} +<div class="row"> + <div class="col-12"> + <h1>NewsLetters</h1> + <a href="{% url 'news:newsletter_create' %}">Add a NewsLetter</a> + <br/> + <ul> + {% for nl in newsletters %} + <li> + <div><a href="{{ nl.get_absolute_url }}">{{ nl }}</a> <strong>Actions:</strong> <a href="{% url 'news:newsletter_update' pk=nl.id %}">Update</a> <a href="{% url 'news:newsletter_delete' pk=nl.id %}">Delete</a> + <h3>Add a News Item to this Newsletter:</h3> + <form class="d-block mt-2 mb-3" action="{% url 'news:add_newsitem_to_newsletter' nlpk=nl.id %}" method="post"> + {% csrf_token %} + {{ add_ni_to_nl_form|bootstrap }} + <input type="submit" name="submit" value="Add" class="btn btn-outline-secondary"> + </form> + </div> + <ul> + {% for nt in nl.newsletternewsitemstable_set.all %} + <li>{{ nt.newsitem }}</li> + {% empty %} + <li>No associated NewsItems found</li> + {% endfor %} + </ul> + </li> + {% endfor %} + </ul> + </div> +</div> + +<div class="row"> + <div class="col-12"> + <h1>NewsItems</h1> + <a href="{% url 'news:newsitem_create' %}">Add a NewsItem</a> + <br/> + <ul> + {% for ni in newsitems %} + <li>{{ ni }} <a href="{% url 'news:newsitem_update' pk=ni.id %}">Update</a> <a href="{% url 'news:newsitem_delete' pk=ni.id %}">Delete</a></li> + {% endfor %} + </ul> + </div> +</div> +{% endblock content %} diff --git a/news/templates/news/newsitem_confirm_delete.html b/news/templates/news/newsitem_confirm_delete.html new file mode 100644 index 0000000000000000000000000000000000000000..a2d678e164e830abe25fb5da88ff797da8640f1b --- /dev/null +++ b/news/templates/news/newsitem_confirm_delete.html @@ -0,0 +1,25 @@ +{% extends 'scipost/base.html' %} + +{% load bootstrap %} + +{% block pagetitle %}: Delete NewsItem{% endblock pagetitle %} + +{% block content %} +<div class="row"> + <div class="col-12"> + <h1 class="highlight">Delete NewsItem</h1> + {{ object }} + </div> +</div> +<div class="row"> + <div class="col-12"> + <form method="post"> + {% csrf_token %} + <h3 class="mb-2">Are you sure you want to delete this NewsItem?</h3> + <input type="submit" class="btn btn-danger" value="Yes, delete it" /> + </form> + </ul> + </div> +</div> + +{% endblock content %} diff --git a/news/templates/news/newsitem_create.html b/news/templates/news/newsitem_create.html new file mode 100644 index 0000000000000000000000000000000000000000..fafb194eb0675d2a731236c9b4077babaf39140f --- /dev/null +++ b/news/templates/news/newsitem_create.html @@ -0,0 +1,16 @@ +{% extends 'scipost/base.html' %} + +{% load bootstrap %} + +{% block pagetitle %}: NewsItems{% endblock pagetitle %} + +{% block content %} +<div class="row"> + <div class="col-12"> + <form action="{% url 'news:newsitem_create' %}" method="post" enctype="multipart/form-data"> + {% csrf_token %} + {{ form|bootstrap }} + <input type="submit" value="Submit" class="btn btn-primary"> + </div> +</div> +{% endblock content %} diff --git a/news/templates/news/newsitem_list.html b/news/templates/news/newsitem_list.html index 01b17aa4c0a1b932bffa869585ae4c24686f4d9a..af456a7bad6786002590219ce970ce214116446b 100644 --- a/news/templates/news/newsitem_list.html +++ b/news/templates/news/newsitem_list.html @@ -9,7 +9,10 @@ <div class="row"> <div class="col-12"> - <h1 class="highlight">SciPost News</h1> + <h1 class="highlight">SciPost News</h1> + {% if perms.scipost.can_manage_news %} + Go to the <a href="{% url 'news:manage' %}">News management page</a> + {% endif %} </div> </div> @@ -29,9 +32,19 @@ {% endif %} {% for item in object_list %} - <div class="card card-grey card-news"> + {% if item.image %} + <div class="row"> + <div class="col-3"> + <img class="d-flex mr-3 {{ item.image.css_class }}" src="{{ item.image.url }}" alt="image"/> + </div> + <div class="col-9"> + {% endif %} + <div class="card card-grey card-news"> {% include 'news/news_card_content.html' with news=item %} - </div> + </div> + {% if item.image %} + </div> + {% endif %} {% empty %} <div>No news found.</div> {% endfor %} diff --git a/news/templates/news/newsitem_update.html b/news/templates/news/newsitem_update.html new file mode 100644 index 0000000000000000000000000000000000000000..aeaf401b2ea77953c0c780e6f9486538591f14ff --- /dev/null +++ b/news/templates/news/newsitem_update.html @@ -0,0 +1,36 @@ +{% extends 'scipost/base.html' %} + +{% load bootstrap %} + +{% block pagetitle %}: NewsItems{% endblock pagetitle %} + +{% block breadcrumb_items %} +{{ block.super }} +<a href="{% url 'news:news' %}" class="breadcrumb-item">News</a> +<a href="{% url 'news:manage' %}" class="breadcrumb-item">Manage</a> +<span class="breadcrumb-item">Update NewsItem</span> +{% endblock breadcrumb_items %} + + +{% block content %} + +<div class="row"> + <div class="col-12"> + <h1>News Item to update:</h1> + <div class="card card-grey card-news"> + {% include 'news/news_card_content.html' with news=object %} + </div> + </div> + + <hr/> + +<div class="row"> + <div class="col-12"> + <h1>Edit it here:</h1> + <form action="{% url 'news:newsitem_update' pk=object.id %}" method="post" enctype="multipart/form-data"> + {% csrf_token %} + {{ form|bootstrap }} + <input type="submit" value="Submit" class="btn btn-primary"> + </div> +</div> +{% endblock content %} diff --git a/news/templates/news/newsletter_confirm_delete.html b/news/templates/news/newsletter_confirm_delete.html new file mode 100644 index 0000000000000000000000000000000000000000..ccd45b70f26d200fbd3ff50607a465955d6c3f5f --- /dev/null +++ b/news/templates/news/newsletter_confirm_delete.html @@ -0,0 +1,25 @@ +{% extends 'scipost/base.html' %} + +{% load bootstrap %} + +{% block pagetitle %}: Delete NewsLetter{% endblock pagetitle %} + +{% block content %} +<div class="row"> + <div class="col-12"> + <h1 class="highlight">Delete NewsLetter</h1> + {{ object }} + </div> +</div> +<div class="row"> + <div class="col-12"> + <form method="post"> + {% csrf_token %} + <h3 class="mb-2">Are you sure you want to delete this NewsLetter?</h3> + <input type="submit" class="btn btn-danger" value="Yes, delete it" /> + </form> + </ul> + </div> +</div> + +{% endblock content %} diff --git a/news/templates/news/newsletter_create.html b/news/templates/news/newsletter_create.html new file mode 100644 index 0000000000000000000000000000000000000000..1313af25d9e64feded62fe20c49e575c9097ab9f --- /dev/null +++ b/news/templates/news/newsletter_create.html @@ -0,0 +1,16 @@ +{% extends 'scipost/base.html' %} + +{% load bootstrap %} + +{% block pagetitle %}: NewsLetters{% endblock pagetitle %} + +{% block content %} +<div class="row"> + <div class="col-12"> + <form action="{% url 'news:newsletter_create' %}" method="post"> + {% csrf_token %} + {{ form|bootstrap }} + <input type="submit" value="Submit" class="btn btn-primary"> + </div> +</div> +{% endblock content %} diff --git a/news/templates/news/newsletter_detail.html b/news/templates/news/newsletter_detail.html new file mode 100644 index 0000000000000000000000000000000000000000..be923e3edb62e9369b89fd51bbaa420378ed8355 --- /dev/null +++ b/news/templates/news/newsletter_detail.html @@ -0,0 +1,15 @@ +{% extends 'scipost/base.html' %} + +{% load bootstrap %} + +{% block pagetitle %}: NewsLetter{% endblock pagetitle %} + +{% block content %} + +<div class="row"> + <div class="col-12"> + {% include 'news/_newsletter_contents.html' with nl=nl %} + </div> +</div> + +{% endblock content %} diff --git a/news/templates/news/newsletter_update.html b/news/templates/news/newsletter_update.html new file mode 100644 index 0000000000000000000000000000000000000000..d3c83391a33e0655a111686d9a12fa40c53c314c --- /dev/null +++ b/news/templates/news/newsletter_update.html @@ -0,0 +1,35 @@ +{% extends 'scipost/base.html' %} + +{% load bootstrap %} + +{% block pagetitle %}: NewsLetters{% endblock pagetitle %} + +{% block breadcrumb_items %} +{{ block.super }} +<a href="{% url 'news:news' %}" class="breadcrumb-item">News</a> +<a href="{% url 'news:manage' %}" class="breadcrumb-item">Manage</a> +<span class="breadcrumb-item">Update NewsLetter</span> +{% endblock breadcrumb_items %} + + +{% block content %} + +<div class="row"> + <div class="col-12"> + <h1>Newsletter to update:</h1> + {{ object }} + </div> +</div> + + <hr/> + +<div class="row"> + <div class="col-12"> + <h1>Edit it here:</h1> + <form action="{% url 'news:newsletter_update' pk=object.id %}" method="post"> + {% csrf_token %} + {{ form|bootstrap }} + <input type="submit" value="Submit" class="btn btn-primary"> + </div> +</div> +{% endblock content %} diff --git a/news/urls.py b/news/urls.py index ac389a486a4510055bbf3b83eb7ecf24248121f8..3c28fe51dc7c993f3f3f300f036f77326fe67451 100644 --- a/news/urls.py +++ b/news/urls.py @@ -7,5 +7,32 @@ from django.conf.urls import url from . import views urlpatterns = [ + url(r'^manage/$', + views.NewsManageView.as_view(), + name='manage'), + url(r'^newsletter/(?P<year>[0-9]{4,})-(?P<month>[0-9]{2,})-(?P<day>[0-9]{2,})/$', + views.NewsLetterView.as_view(), + name='newsletter_detail'), + url(r'^newsletter/add/$', + views.NewsLetterCreateView.as_view(), + name='newsletter_create'), + url(r'^newsletter/(?P<pk>[0-9]+)/update/$', + views.NewsLetterUpdateView.as_view(), + name='newsletter_update'), + url(r'^newsletter/(?P<pk>[0-9]+)/delete/$', + views.NewsLetterDeleteView.as_view(), + name='newsletter_delete'), + url(r'^newsitem/add/$', + views.NewsItemCreateView.as_view(), + name='newsitem_create'), + url(r'^newsitem/(?P<pk>[0-9]+)/update/$', + views.NewsItemUpdateView.as_view(), + name='newsitem_update'), + url(r'^newsitem/(?P<pk>[0-9]+)/delete/$', + views.NewsItemDeleteView.as_view(), + name='newsitem_delete'), + url(r'^add_newsitem_to_newsletter/(?P<nlpk>[0-9]+)/$', + views.NewsLetterNewsItemsTableCreateView.as_view(), + name='add_newsitem_to_newsletter'), url(r'^$', views.NewsListView.as_view(), name='news'), ] diff --git a/news/views.py b/news/views.py index f56a846d3b60bed5b5348ddf47f9d8ed739ba295..377fd677542ed71f800639cfe1ad009ef9d882f0 100644 --- a/news/views.py +++ b/news/views.py @@ -2,9 +2,124 @@ __copyright__ = "Copyright 2016-2018, Stichting SciPost (SciPost Foundation)" __license__ = "AGPL v3" +from django.contrib import messages +from django.core.urlresolvers import reverse_lazy +from django.shortcuts import get_object_or_404 +from django.views.generic.base import TemplateView +from django.views.generic.edit import CreateView, UpdateView, DeleteView +from django.views.generic.detail import DetailView from django.views.generic.list import ListView -from .models import NewsItem +from .models import NewsLetter, NewsItem +from .forms import NewsLetterForm, NewsItemForm, NewsLetterNewsItemsTableForm + +from scipost.mixins import PermissionsMixin + + +class NewsManageView(PermissionsMixin, TemplateView): + """ + General management of News. + """ + permission_required = 'scipost.can_manage_news' + template_name = 'news/news_manage.html' + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['newsletters'] = NewsLetter.objects.all() + context['newsitems'] = NewsItem.objects.all() + context['add_ni_to_nl_form'] = NewsLetterNewsItemsTableForm() + return context + + +class NewsLetterView(TemplateView): + """ + Newsletter, for public consumption online. + """ + template_name = 'news/newsletter_detail.html' + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['nl'] = get_object_or_404(NewsLetter, + date__year=self.kwargs['year'], + date__month=self.kwargs['month'], + date__day=self.kwargs['day']) + return context + + +class NewsLetterCreateView(PermissionsMixin, CreateView): + """ + Create a NewsLetter. + """ + permission_required = 'scipost.can_manage_news' + form_class = NewsLetterForm + template_name = 'news/newsletter_create.html' + success_url = reverse_lazy('news:manage') + + +class NewsLetterUpdateView(PermissionsMixin, UpdateView): + """ + Update a NewsLetter. + """ + permission_required = 'scipost.can_manage_news' + model = NewsLetter + form_class = NewsLetterForm + template_name = 'news/newsletter_update.html' + success_url = reverse_lazy('news:news') + + +class NewsLetterDeleteView(PermissionsMixin, DeleteView): + """ + Delete a NewsLetter. + """ + permission_required = 'scipost.can_manage_news' + model = NewsLetter + success_url = reverse_lazy('news:news') + + +class NewsItemCreateView(PermissionsMixin, CreateView): + """ + Create a NewsItem. + """ + permission_required = 'scipost.can_manage_news' + form_class = NewsItemForm + template_name = 'news/newsitem_create.html' + success_url = reverse_lazy('news:news') + + +class NewsItemUpdateView(PermissionsMixin, UpdateView): + """ + Update a NewsItem. + """ + permission_required = 'scipost.can_manage_news' + model = NewsItem + form_class = NewsItemForm + template_name = 'news/newsitem_update.html' + success_url = reverse_lazy('news:news') + + +class NewsItemDeleteView(PermissionsMixin, DeleteView): + """ + Delete a NewsItem. + """ + permission_required = 'scipost.can_manage_news' + model = NewsItem + success_url = reverse_lazy('news:news') + + +class NewsLetterNewsItemsTableCreateView(PermissionsMixin, CreateView): + """ + Add a NewsItem to a NewsLetter. + """ + permission_required = 'scipost.can_manage_news' + form_class = NewsLetterNewsItemsTableForm + success_url = reverse_lazy('news:manage') + + def form_valid(self, form): + nl = get_object_or_404(NewsLetter, id=self.kwargs['nlpk']) + form.instance.newsletter = nl + form.instance.order = nl.newsletternewsitemstable_set.all().count() + 1 + messages.success(self.request, 'Successfully added News Item to Newsletter') + return super().form_valid(form) class NewsListView(ListView): diff --git a/scipost/management/commands/add_groups_and_permissions.py b/scipost/management/commands/add_groups_and_permissions.py index 6e060bd60948f810bfaca8bc1ea7e233e2bf6fca..e035ba9ca26052e6cea4854b453e4240e6c752bc 100644 --- a/scipost/management/commands/add_groups_and_permissions.py +++ b/scipost/management/commands/add_groups_and_permissions.py @@ -28,6 +28,7 @@ class Command(BaseCommand): name='Registered Contributors') Developers, created = Group.objects.get_or_create(name='Developers') Testers, created = Group.objects.get_or_create(name='Testers') + NewsAdmin, created = Group.objects.get_or_create(name='News Administrators') Ambassadors, created = Group.objects.get_or_create(name='Ambassadors') JuniorAmbassadors, created = Group.objects.get_or_create(name='Junior Ambassadors') ProductionSupervisors, created = Group.objects.get_or_create(name='Production Supervisor') @@ -280,6 +281,12 @@ class Command(BaseCommand): name='Can manage affiliations', content_type=content_type) + # News administration + can_manage_news, created = Permission.objects.get_or_create( + codename='can_manage_news', + name='Can manage News', + content_type=content_type) + # Mailchimp can_manage_mailchimp, created = Permission.objects.get_or_create( codename='can_manage_mailchimp',