Skip to content

Commit

Permalink
Merge branch 'develop' into 855-article-sidebar-mobile
Browse files Browse the repository at this point in the history
  • Loading branch information
Rowansdabomb authored Aug 13, 2018
2 parents f6be71e + b5b3bf5 commit 3953fdd
Show file tree
Hide file tree
Showing 34 changed files with 993 additions and 29 deletions.
2 changes: 1 addition & 1 deletion dispatch/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '0.4.29'
__version__ = '0.4.30'
92 changes: 90 additions & 2 deletions dispatch/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

from dispatch.modules.content.models import (
Article, Image, ImageAttachment, ImageGallery, Issue,
File, Page, Author, Section, Tag, Topic, Video, VideoAttachment, Poll, PollAnswer, PollVote)
File, Page, Author, Section, Tag, Topic, Video,
VideoAttachment, Poll, PollAnswer, PollVote, Subsection)
from dispatch.modules.auth.models import Person, User, Invite
from dispatch.admin.registration import send_invitation
from dispatch.theme.exceptions import WidgetNotFound, InvalidField
Expand Down Expand Up @@ -520,6 +521,83 @@ def insert_data(self, content):

return map(self.insert_instance, content)

class SubsectionArticleSerializer(DispatchModelSerializer):
"""Serializes articles for the Subsection model"""
id = serializers.ReadOnlyField(source='parent_id')
authors = AuthorSerializer(many=True, read_only=True)

class Meta:
model = Article
fields = (
'id',
'headline',
'authors',
'published_version'
)

class SubsectionSerializer(DispatchModelSerializer):
"""Serializes the Subsection model"""

authors = AuthorSerializer(many=True, read_only=True)
author_ids = serializers.ListField(
write_only=True,
child=serializers.JSONField(),
validators=[AuthorValidator]
)
authors_string = serializers.CharField(source='get_author_string', read_only=True)
articles = SubsectionArticleSerializer(many=True, read_only=True, source='get_articles')
article_ids = serializers.ListField(
write_only=True,
child=serializers.JSONField(),
required=False
)
section = SectionSerializer(read_only=True)
section_id = serializers.IntegerField(write_only=True)

slug = serializers.SlugField(validators=[SlugValidator()])

class Meta:
model = Subsection
fields = (
'id',
'is_active',
'name',
'section',
'section_id',
'slug',
'description',
'authors',
'author_ids',
'authors_string',
'articles',
'article_ids'
)

def create(self, validated_data):
instance = Subsection()
return self.update(instance, validated_data)

def update(self, instance, validated_data):
# Update basic fields
instance.name = validated_data.get('name', instance.name)
instance.slug = validated_data.get('slug', instance.slug)
instance.is_active = validated_data.get('is_active', instance.is_active)
instance.section_id = validated_data.get('section_id', instance.section_id)
instance.description = validated_data.get('description', instance.description)

# Save instance before processing/saving content in order to save associations to correct ID
instance.save()

authors = validated_data.get('author_ids')

instance.save_authors(authors, is_publishable=False)

article_ids = validated_data.get('article_ids')

instance.save_articles(article_ids)

return instance

class ArticleSerializer(DispatchModelSerializer, DispatchPublishableSerializer):
"""Serializes the Article model."""

Expand All @@ -529,6 +607,9 @@ class ArticleSerializer(DispatchModelSerializer, DispatchPublishableSerializer):
section = SectionSerializer(read_only=True)
section_id = serializers.IntegerField(write_only=True)

subsection = SubsectionSerializer(source='get_subsection', read_only=True)
subsection_id = serializers.IntegerField(write_only=True, required=False, allow_null=True)

featured_image = ImageAttachmentSerializer(required=False, allow_null=True)
featured_video = VideoAttachmentSerializer(required=False, allow_null=True)

Expand Down Expand Up @@ -585,6 +666,8 @@ class Meta:
'authors_string',
'section',
'section_id',
'subsection',
'subsection_id',
'published_at',
'is_published',
'is_breaking',
Expand Down Expand Up @@ -642,7 +725,7 @@ def update(self, instance, validated_data):
instance.integrations = validated_data.get('integrations', instance.integrations)
instance.template = template
instance.template_data = template_data

instance.save()

instance.content = validated_data.get('content', instance.content)
Expand All @@ -656,6 +739,7 @@ def update(self, instance, validated_data):
instance.save_featured_video(featured_video)

authors = validated_data.get('author_ids')

if authors:
instance.save_authors(authors, is_publishable=True)

Expand All @@ -666,6 +750,10 @@ def update(self, instance, validated_data):
if topic_id != False:
instance.save_topic(topic_id)

subsection_id = validated_data.get('subsection_id', None)
if subsection_id is not None:
instance.save_subsection(subsection_id)

# Perform a final save (without revision), update content and featured image
instance.save(
update_fields=['content', 'featured_image', 'featured_video', 'topic'],
Expand Down
1 change: 1 addition & 0 deletions dispatch/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
router.register(r'videos', views.VideoViewSet, base_name='api-videos')
router.register(r'invites', views.InviteViewSet, base_name='api-invites')
router.register(r'polls', views.PollViewSet, base_name='api-polls')
router.register(r'subsections', views.SubsectionViewSet, base_name='api-subsections')

dashboard_recent_articles = views.DashboardViewSet.as_view({'get': 'list_recent_articles'})
dashboard_user_actions = views.DashboardViewSet.as_view({'get': 'list_actions'})
Expand Down
24 changes: 21 additions & 3 deletions dispatch/api/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

from rest_framework.exceptions import ValidationError
from dispatch.theme.exceptions import InvalidField, TemplateNotFound
from django.contrib.contenttypes.models import ContentType

from django.contrib.auth.password_validation import validate_password

from dispatch.api.exceptions import InvalidFilename, InvalidGalleryAttachments
from dispatch.models import Image, Person
from dispatch.models import Image, Person, Section, Page, Subsection, Article

class PasswordValidator(object):
def __init__(self, confirm_field):
Expand Down Expand Up @@ -44,13 +45,30 @@ def set_context(self, serializer_field):
self.model = serializer_field.parent.Meta.model

def __call__(self, slug):
if self.instance is None:
if self.model.__name__ is 'Subsection':
self.validate_subsection_slug(slug)
elif self.instance is None:
if self.model.objects.filter(slug=slug).exists():
raise ValidationError('%s with slug \'%s\' already exists.' % (self.model.__name__, slug))
else:
if self.model.objects.filter(slug=slug).exclude(parent=self.instance.parent).exists():
raise ValidationError('%s with slug \'%s\' already exists.' % (self.model.__name__, slug))

def validate_subsection_slug(self, slug):
if Section.objects.filter(slug=slug).exists():
raise ValidationError('A section with that slug already exists.')
if Page.objects.filter(slug=slug).exists():
raise ValidationError('A page with that slug already exists')

if self.instance is None:
if self.model.objects.filter(slug=slug).exists():
raise ValidationError('%s with slug \'%s\' already exists.' % (self.model.__name__, slug))
else:
if self.model.objects.filter(slug=slug).exclude(id=self.instance.id):
raise ValidationError('%s with slug \'%s\' already exists.' % (self.model.__name__, slug))



def AuthorValidator(data):
"""Raise a ValidationError if data does not match the author format."""
if not isinstance(data, list):
Expand Down Expand Up @@ -90,4 +108,4 @@ def TemplateValidator(template, template_data, tags):
errors['instructions'] = 'Must have a corresponding timeline tag'

if errors:
raise ValidationError(errors)
raise ValidationError(errors)
26 changes: 22 additions & 4 deletions dispatch/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from dispatch.models import (
Article, File, Image, ImageAttachment, ImageGallery, Issue,
Page, Author, Person, Section, Tag, Topic, User, Video,
Poll, PollAnswer, PollVote, Invite)
Poll, PollAnswer, PollVote, Invite, Subsection)

from dispatch.core.settings import get_settings
from dispatch.admin.registration import reset_password
Expand All @@ -31,7 +31,7 @@
FileSerializer, IssueSerializer, ImageGallerySerializer, TagSerializer,
TopicSerializer, PersonSerializer, UserSerializer, IntegrationSerializer,
ZoneSerializer, WidgetSerializer, TemplateSerializer, VideoSerializer,
PollSerializer, PollVoteSerializer, InviteSerializer)
PollSerializer, PollVoteSerializer, InviteSerializer, SubsectionSerializer)
from dispatch.api.exceptions import (
ProtectedResourceError, BadCredentials, PollClosed, InvalidPoll,
UnpermittedActionError)
Expand Down Expand Up @@ -87,7 +87,7 @@ def get_queryset(self):
'authors'
)

queryset = queryset.order_by('-updated_at')
queryset = queryset.order_by('-updated_at')

q = self.request.query_params.get('q', None)
section = self.request.query_params.get('section', None)
Expand All @@ -99,7 +99,7 @@ def get_queryset(self):

if section is not None:
queryset = queryset.filter(section_id=section)

if tags is not None:
for tag in tags:
queryset = queryset.filter(tags__id=tag)
Expand All @@ -109,6 +109,24 @@ def get_queryset(self):

return queryset

class SubsectionViewSet(DispatchModelViewSet):
"""Viewset for the Subsection model views."""
model = Subsection
serializer_class = SubsectionSerializer

def get_queryset(self):
queryset = Subsection.objects.all()
q = self.request.query_params.get('q', None)
section = self.request.query_params.get('section', None)

if q is not None:
queryset = queryset.filter(name__icontains=q)

if section is not None:
queryset = queryset.filter(section_id=section)

return queryset

class PageViewSet(DispatchModelViewSet, DispatchPublishableMixin):
"""Viewset for Page model views."""
model = Page
Expand Down
35 changes: 35 additions & 0 deletions dispatch/migrations/0017_subsections.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-08-10 20:30
from __future__ import unicode_literals

import dispatch.modules.content.mixins
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('dispatch', '0016_breaking_news'),
]

operations = [
migrations.CreateModel(
name='Subsection',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100, unique=True)),
('slug', models.SlugField(unique=True)),
('description', models.TextField(null=True)),
('is_active', models.BooleanField(default=False)),
('authors', models.ManyToManyField(related_name='subsection_authors', to='dispatch.Author')),
('section', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dispatch.Section')),
],
bases=(models.Model, dispatch.modules.content.mixins.AuthorMixin),
),
migrations.AddField(
model_name='article',
name='subsection',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='article_subsection', to='dispatch.Subsection'),
),
]
43 changes: 36 additions & 7 deletions dispatch/modules/content/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from django.db.models import (
Model, DateTimeField, CharField, TextField, PositiveIntegerField,
ImageField, FileField, BooleanField, UUIDField, ForeignKey,
ManyToManyField, SlugField, SET_NULL, CASCADE)
ManyToManyField, SlugField, SET_NULL, CASCADE, F)
from django.conf import settings
from django.core.validators import MaxValueValidator
from django.utils import timezone
Expand Down Expand Up @@ -307,6 +307,7 @@ class Article(Publishable, AuthorMixin):

headline = CharField(max_length=255)
section = ForeignKey('Section')
subsection = ForeignKey('Subsection', related_name='article_subsection', blank=True, null=True)
authors = ManyToManyField('Author', related_name='article_authors')
topic = ForeignKey('Topic', null=True)
tags = ManyToManyField('Tag')
Expand Down Expand Up @@ -372,11 +373,41 @@ def save_topic(self, topic_id):
pass

def get_absolute_url(self):
"""
Returns article URL.
"""
""" Returns article URL. """
return "%s%s/%s/" % (settings.BASE_URL, self.section.slug, self.slug)

def get_subsection(self):
""" Returns the subsection set in the parent article """
return self.parent.subsection

def save_subsection(self, subsection_id):
""" Save the subsection to the parent article """
Article.objects.filter(parent_id=self.parent.id).update(subsection_id=subsection_id)

class Subsection(Model, AuthorMixin):
name = CharField(max_length=100, unique=True)
slug = SlugField(unique=True)
description = TextField(null=True)
authors = ManyToManyField('Author', related_name='subsection_authors')
section = ForeignKey('Section')
is_active = BooleanField(default=False)

AuthorModel = Author

def save_articles(self, article_ids):
Article.objects.filter(subsection=self).update(subsection=None)
Article.objects.filter(parent_id__in=article_ids).update(subsection=self)

def get_articles(self):
return Article.objects.filter(subsection=self, id=F('parent_id'))

def get_published_articles(self):
return Article.objects.filter(subsection=self, is_published=True)

def get_absolute_url(self):
""" Returns the subsection URL. """
return "%s%s/" % (settings.BASE_URL, self.slug)

class Page(Publishable):
parent = ForeignKey('Page', related_name='page_parent', blank=True, null=True)
parent_page = ForeignKey('Page', related_name='parent_page_fk', null=True)
Expand All @@ -386,9 +417,7 @@ def get_author_string(self):
return None

def get_absolute_url(self):
"""
Returns page URL.
"""
""" Returns page URL. """
return "%s%s/" % (settings.BASE_URL, self.slug)

class Video(Model):
Expand Down
2 changes: 1 addition & 1 deletion dispatch/static/manager/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dispatch",
"version": "0.4.29",
"version": "0.4.30",
"description": "The frontend framework for the Dispatch publishing platform.",
"author": "Peter Siemens <[email protected]>",
"homepage": "https://www.ubyssey.ca",
Expand Down
2 changes: 2 additions & 0 deletions dispatch/static/manager/src/js/actions/ArticlesActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ class ArticlesActions extends PublishableActions {

data.section_id = data.section

data.subsection_id = data.subsection

data.author_ids = data.authors

data.tag_ids = data.tags
Expand Down
Loading

0 comments on commit 3953fdd

Please sign in to comment.