Skip to content

Commit

Permalink
Merge branch 'develop' of github.com:ubyssey/dispatch into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
Razvan Nesiu committed Jul 19, 2019
2 parents 68f6baa + 9a9f486 commit c09c814
Show file tree
Hide file tree
Showing 25 changed files with 352 additions and 173 deletions.
24 changes: 20 additions & 4 deletions dispatch/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from rest_framework.validators import UniqueValidator

from django.conf import settings
import json

from dispatch.modules.content.models import (
Article, Image, ImageAttachment, ImageGallery, Issue,
Expand Down Expand Up @@ -93,7 +94,7 @@ class Meta:
)

def create(self, validated_data):
instance = User.objects.create_user(validated_data['email'], validated_data['password_a'], validated_data['permission_level'], validated_data['person'])
instance = User.objects.create_user(validated_data['email'], validated_data['password_a'], validated_data['permission_level'])
return self.update(instance, validated_data)

def update(self, instance, validated_data):
Expand Down Expand Up @@ -211,6 +212,8 @@ class VideoSerializer(DispatchModelSerializer):
authors = AuthorSerializer(many=True, read_only=True)
author_ids = serializers.ListField(
write_only=True,
allow_empty=False,
required=True,
child=serializers.JSONField(),
validators=[AuthorValidator(True)])

Expand Down Expand Up @@ -266,14 +269,15 @@ class ImageSerializer(serializers.HyperlinkedModelSerializer):
authors = AuthorSerializer(many=True, read_only=True)
author_ids = serializers.ListField(
write_only=True,
allow_empty=False,
required=True,
child=serializers.JSONField(),
validators=[AuthorValidator(False)])

tags = TagSerializer(many=True, read_only=True)
tag_ids = serializers.ListField(
write_only=True,
required=False,
child=serializers.IntegerField())
required=False)

width = serializers.IntegerField(read_only=True)
height = serializers.IntegerField(read_only=True)
Expand Down Expand Up @@ -305,11 +309,21 @@ def update(self, instance, validated_data):
instance = super(ImageSerializer, self).update(instance, validated_data)

# Save authors

authors = validated_data.get('author_ids')

if authors and isinstance(authors, list) and len(authors) == 1 and isinstance(authors[0], str):
authors = authors[0][1:-1].replace('},{', '};;{').split(';;')
authors = [json.loads(author) for author in authors]

if authors:
instance.save_authors(authors)

tag_ids = validated_data.get('tag_ids', False)

if tag_ids and isinstance(tag_ids, list) and len(tag_ids) == 1 and isinstance(tag_ids[0], str):
tag_ids = [int(tag) for tag in tag_ids[0].split(',')]

if tag_ids != False:
instance.save_tags(tag_ids)

Expand Down Expand Up @@ -664,6 +678,8 @@ class ArticleSerializer(DispatchModelSerializer, DispatchPublishableSerializer):
authors = AuthorSerializer(many=True, read_only=True)
author_ids = serializers.ListField(
write_only=True,
allow_empty=False,
required=True,
child=serializers.JSONField(),
validators=[AuthorValidator(False)])
authors_string = serializers.CharField(source='get_author_string', read_only=True)
Expand Down Expand Up @@ -1131,4 +1147,4 @@ class Meta:
if settings.GS_USE_SIGNED_URLS:
fields += (
'file_upload_url',
)
)
16 changes: 13 additions & 3 deletions dispatch/api/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,22 @@ def __call__(self, data):
# Convert single instance to a list
data = [data]

AUTHOR_TYPES = {'author', 'photographer', 'illustrator', 'videographer'}

for author in data:
if 'person' not in author:
raise ValidationError('An author must contain a person.')
if 'type' in author and not isinstance(author['type'], str):
# If type is defined, it should be a string
raise ValidationError('The author type must be a string.')
if 'type' in author:
if isinstance(author, dict):
if not isinstance(author['type'], str) or author['type'].lower() not in AUTHOR_TYPES:
raise ValidationError('The author type must be a string, matching a predefined type.')
elif isinstance(author, str):
tokens = author.split('"')
for i in range(0, len(tokens)):
if 5 + 6 * i < len(tokens):
author_type = tokens[5 + 6 * i]
if author_type.lower() not in AUTHOR_TYPES:
raise ValidationError('The author type must be a string, matching a predefined type.')

def TemplateValidator(template, template_data, tags, subsection_id):

Expand Down
3 changes: 0 additions & 3 deletions dispatch/modules/auth/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ def _create_user(self, email, password=None, permissions=None, is_active=True, i
if not self.is_valid_password(password):
raise ValueError('Password is invalid')

if not person:
raise ValueError('User must have a valid person')

user = self.model(email=email, is_active=is_active, is_superuser=is_superuser)
user.set_password(password)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import HTML5Backend from 'react-dnd-html5-backend'
import { DragDropContext } from 'react-dnd'
export default DragDropContext(HTML5Backend)
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import R from 'ramda'
import React from 'react'
import { connect } from 'react-redux'
import { DragDropContext } from 'react-dnd'
import HTML5Backend from 'react-dnd-html5-backend'
import DragDropContext from './DragDropContext'
import Measure from 'react-measure'
import autobind from 'class-autobind'

Expand Down Expand Up @@ -307,10 +306,9 @@ const mapDispatchToProps = (dispatch) => {
}
}


const GalleryForm = connect(
mapStateToProps,
mapDispatchToProps
)(DragDropContext(HTML5Backend)(GalleryFormComponent))
)(DragDropContext(GalleryFormComponent))

export default GalleryForm
export default GalleryForm
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export default class ImageForm extends React.Component {
<AuthorSelectInput
value={this.props.listItem.authors}
update={authors => this.props.update('authors', authors)}
authorErrors={this.props.authorErrors}
defaultAuthorType={AuthorSelectInput.PHOTOGRAPHER} />
</Form.Input>

Expand Down
22 changes: 19 additions & 3 deletions dispatch/static/manager/src/js/components/ItemEditor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ const NEW_LISTITEM_ID = 'new'

class ItemEditor extends React.Component {

constructor(props) {
super(props)

this.state = {
noAuthorError: null
}
}

componentDidMount() {
if (this.props.isNew) {
// Create empty listItem
Expand Down Expand Up @@ -53,19 +61,26 @@ class ItemEditor extends React.Component {
}

saveListItem() {
const listItem = this.getListItem()
if (listItem.authors && !listItem.authors.length) {
this.setState({ noAuthorError: 'This field is required.' })
return
}

if (this.props.isNew) {
this.props.createListItem(this.props.token, this.getListItem())
this.props.createListItem(this.props.token, listItem)
} else {
this.props.saveListItem(
this.props.token,
this.props.itemId,
this.getListItem()
listItem
)
}
}

handleUpdate(field, value) {
this.props.setListItem(R.assoc(field, value, this.getListItem()))
if(field == 'authors' && value.length) { this.setState({ noAuthorError: null }) }
}

render() {
Expand Down Expand Up @@ -94,6 +109,7 @@ class ItemEditor extends React.Component {
<this.props.form
listItem={listItem}
errors={this.props.listItem ? this.props.listItem.errors : {}}
authorErrors={this.state.noAuthorError}
update={(field, value) => this.handleUpdate(field, value)}
settings={this.props.settings ? this.props.settings : {}} />
</div>
Expand All @@ -108,4 +124,4 @@ ItemEditor.defaultProps = {
multipart: false
}

export default withRouter(ItemEditor)
export default withRouter(ItemEditor)
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export default function SubsectionForm(props) {
error={props.errors.author_ids}>
<AuthorSelectInput
value={props.listItem.authors || []}
authorErrors={props.authorErrors}
update={authors => props.update('authors', authors)} />
</Form.Input>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export default function VideoForm(props) {
<AuthorSelectInput
value={props.listItem.authors ? props.listItem.authors: []}
update={authors => props.update('authors', authors)}
authorErrors={props.authorErrors}
defaultAuthorType={AuthorSelectInput.VIDEOGRAPHER} />
</Form.Input>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import React from 'react'
import R from 'ramda'
import PropTypes from 'prop-types'
import { DragDropContext } from 'react-dnd'
import HTML5Backend from 'react-dnd-html5-backend'

import DragDropContext from '../../GalleryEditor/DragDropContext'
import Item from './Item'

require('../../../../styles/components/sortable_list.scss')
Expand Down Expand Up @@ -55,4 +53,4 @@ SortableList.defaultProps = {
inline: false
}

export default DragDropContext(HTML5Backend)(SortableList)
export default DragDropContext(SortableList)
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,14 @@ class AuthorSelectInputComponent extends React.Component {
}

render() {
const value = this.props.value
.map(author => author.person)

const extraFields = this.props.value
let value = []
let extraFields = []

if(this.props.value){
value = this.props.value.map(author => author.person)
extraFields = this.props.value
.reduce((fields, author) => R.assoc(author.person, author.type, fields), {})
}

return (
<ItemSelectInput
Expand All @@ -55,7 +58,8 @@ class AuthorSelectInputComponent extends React.Component {
fetchResults={(query) => this.listPersons(query)}
extraFieldOptions={AUTHOR_TYPES}
attribute='full_name'
editMessage={this.props.value.length ? 'Edit authors' : 'Add authors'} />
errors={this.props.authorErrors}
editMessage={this.props.value && this.props.value.length ? 'Edit authors' : 'Add authors'} />
)
}
}
Expand Down Expand Up @@ -93,4 +97,4 @@ AuthorSelectInput.defaultProps = {
defaultAuthorType: AuthorSelectInput.AUTHOR
}

export default AuthorSelectInput
export default AuthorSelectInput
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,12 @@ class ItemSelectInput extends React.Component {
</Tag>
)

const error = (
<span className='c-form__input__error bp3-tag bp3-intent-danger'>
{Array.isArray(this.props.errors) ? this.props.errors.join(' ') : this.props.errors}
</span>
)

return (
<div
className={`c-input c-input--item-select ${this.props.className}`}>
Expand All @@ -261,6 +267,7 @@ class ItemSelectInput extends React.Component {
inline={this.props.inline}>
{this.props.tag ? tagButton : Button}
</Dropdown>
{this.props.errors && error}
</div>
)
}
Expand All @@ -276,4 +283,4 @@ ItemSelectInput.defaultProps = {
inline: true
}

export default ItemSelectInput
export default ItemSelectInput
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class TagSelectInputComponent extends React.Component {
fetchResults={(query) => this.listTags(query)}
create={(name, cb) => this.props.createTag(this.props.token, { name }, cb)}
attribute='name'
editMessage={this.props.value.length ? 'Edit tags' : 'Add tags'} />
editMessage={this.props.value && this.props.value.length ? 'Edit tags' : 'Add tags'} />
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ export default function ImagePanel(props) {
<div className='c-image-panel__header'>
<Button
intent={Intent.SUCCESS}
onClick={() => props.save()}>Update</Button>
onClick={() => {props.save()}}>{props.successBtnName || 'Update'}</Button>
<Button
intent={Intent.DANGER}
onClick={() => props.delete()}>Delete</Button>
onClick={() => props.delete()}>{props.dangerBtnName || 'Delete'}</Button>
</div>
<div className='c-image-panel__image'>
<img className='c-image-panel__image__img' src={props.image.url_medium} />
<div className='c-image-panel__image__filename'>{props.image.filename}</div>
<img className='c-image-panel__image__img' src={props.image.url_medium? props.image.url_medium : props.image.img.preview} />
<div className='c-image-panel__image__filename'>{props.image.filename? props.image.filename: props.image.img.name}</div>
</div>
<form className='c-image-panel__form'>
<Form.Input label='Title'>
Expand All @@ -35,7 +35,8 @@ export default function ImagePanel(props) {
<Form.Input label='Photographers'>
<AuthorSelectInput
value={props.image.authors}
update={authors => props.update('authors', authors)}
update={authors => {props.update('authors', authors)}}
authorErrors={props.authorErrors}
defaultAuthorType={AuthorSelectInput.PHOTOGRAPHER} />
</Form.Input>
<Form.Input label='Tags'>
Expand All @@ -46,4 +47,4 @@ export default function ImagePanel(props) {
</form>
</div>
)
}
}
Loading

0 comments on commit c09c814

Please sign in to comment.