Skip to content

Commit

Permalink
Merge pull request #29 from unlimitedlabs/journalism_workflow
Browse files Browse the repository at this point in the history
Journalism workflow
  • Loading branch information
thisisdhaas committed Sep 28, 2015
2 parents 6a854ee + acf8464 commit 5741194
Show file tree
Hide file tree
Showing 31 changed files with 1,228 additions and 6 deletions.
1 change: 1 addition & 0 deletions example_project/example_project/orchestra_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ def setup_orchestra(settings_module_name):
# Installed orchestra workflows
settings.ORCHESTRA_PATHS = (
('simple_workflow.workflow', 'simple_workflow'),
('journalism_workflow.workflow', 'journalism_workflow'),
)

# The maximum number of tasks an expert can pick up at a time.
Expand Down
1 change: 1 addition & 0 deletions example_project/example_project/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
'django.contrib.messages',
'django.contrib.staticfiles',
'simple_workflow',
'journalism_workflow',
)

STATICFILES_FINDERS = (
Expand Down
91 changes: 91 additions & 0 deletions example_project/journalism_workflow_ctl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
from pprint import pprint

import argparse
import os
import sys

# Set up the standalone Django script
proj_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
"../")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example_project.settings")
sys.path.append(proj_path)
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()

from orchestra.orchestra_api import create_orchestra_project
from orchestra.orchestra_api import get_project_information


def main(args):
if args.new:
project_id = create_project()
project_info(project_id)
return

elif args.status:
project_info(args.project_id)

elif args.final:
p_info = project_info(args.project_id, verbose=False)
try:
assert p_info['tasks']['copy_editing']['status'] == 'Complete'
pprint(p_info['tasks']['copy_editing']['latest_data'])
except Exception:
print("The '--final' option must be used with a completed project")
sys.exit()


def create_project():
project_id = create_orchestra_project(
None,
'journalism',
'A test run of our journalism workflow',
10,
{
'article_draft_template': '1F9ULJ_eoJFz1whqjK2thsC6gJup2f35IsUUpbcizcfA' # noqa
},
'https://docs.google.com/document/d/1s0IJycNAwHtZfsUwyo6lCJ7kI9pTOZddcaiRDdZUSAs', # noqa
'train',
)
print('Project with id {} created!'.format(project_id))
return project_id


def project_info(project_id, verbose=True):
p_info = get_project_information(project_id)
if verbose:
print("Information received! Here's what we got:")
print('')
pprint(p_info)
return p_info


if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-p', '--project-id',
help='Existing project id to check status of')
primary_command_group = parser.add_mutually_exclusive_group(required=True)
primary_command_group.add_argument('-n', '--new', action='store_true',
help='Start a new journalism project')
primary_command_group.add_argument('-s', '--status', action='store_true',
help=('Check the status of an '
'in-progress project'))
primary_command_group.add_argument('-f', '--final', action='store_true',
help=('Output a final summary of the '
'project'))
args = parser.parse_args()

if args.new and args.project_id:
print("The '--new' option cannot be used in conjunction with a "
"project id.")
sys.exit()
elif args.status and not args.project_id:
print("The '--status' option must be used in conjunction with a "
"project id.")
sys.exit()
elif args.final and not args.project_id:
print("The '--final' option must be used in conjunction with a "
"project id.")
sys.exit()

main(args)
Empty file added journalism_workflow/__init__.py
Empty file.
76 changes: 76 additions & 0 deletions journalism_workflow/adjust_photos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import os
import tempfile

from django.conf import settings
from PIL import Image

from orchestra.google_apps.convenience import create_folder_with_permissions
from orchestra.google_apps.convenience import download_file
from orchestra.google_apps.convenience import upload_file
from orchestra.google_apps.permissions import write_with_link_permission
from orchestra.google_apps.service import Service

import logging
logger = logging.getLogger(__name__)


def autoadjust_photos(project_data, prerequisites):
"""Resize all images in a google drive directory."""
task_data = {}
parent_folder_id = project_data['project_folder_id']

# Create a directory to output the photos
output_folder = create_folder_with_permissions(
parent_folder_id,
'Processed Photos',
permissions=[write_with_link_permission],
)
task_data['processed_photo_folder'] = output_folder['id']

# List the existing photos
raw_photo_folder_id = (prerequisites
.get('photography')
.get('prerequisites')
.get('article_planning')
.get('prerequisites')
.get('document_creation')
.get('task')
.get('data')
.get('raw_photo_folder'))
service = Service(settings.GOOGLE_P12_PATH,
settings.GOOGLE_SERVICE_EMAIL)
photos_metadata = service.list_folder(raw_photo_folder_id)

# Iterate over the input photos and process them.
task_data['photos_for_caption'] = []
for photo_metadata in photos_metadata:
photo, title, mimetype = download_file(photo_metadata)
adjusted_photo_tmpfile = adjust_photo(photo)
upload = upload_file(
task_data['processed_photo_folder'],
adjusted_photo_tmpfile.name,
title,
'image',
mimetype
)
os.unlink(adjusted_photo_tmpfile.name)
embed_link = upload['webContentLink'].replace('&export=download', '')
task_data['photos_for_caption'].append(embed_link)
return task_data


def adjust_photo(photo):

# Write the photo to a temporary file
temp = tempfile.NamedTemporaryFile(mode='wb', delete=False)
temp.write(photo)

# Open it up and play with it in PIL
im = Image.open(temp.name)
im = im.convert('L') # convert to greyscale

# Save it back out into a new temporary file
os.unlink(temp.name)
temp2 = tempfile.NamedTemporaryFile(mode='wb', delete=False)
im.save(temp2, format='jpeg')
return temp2
37 changes: 37 additions & 0 deletions journalism_workflow/documents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from orchestra.google_apps.convenience import create_document_from_template
from orchestra.google_apps.convenience import create_folder_with_permissions
from orchestra.google_apps.permissions import write_with_link_permission

import logging
logger = logging.getLogger(__name__)


def create_documents(project_data, prerequisites):
"""Create documents and folders needed for the journalism workflow.
The following will be created:
* an 'Article Draft' document where a reporter can draft text.
* a 'Raw Photos' folder where a photographer can upload images.
Documents are created in the project root folder.
"""
task_data = {}
folder_id = project_data['project_folder_id']

# Create an Article Draft document.
article_draft_template = project_data['article_draft_template']
task_data['articleURL'] = create_document_from_template(
article_draft_template,
'Article Draft',
parent_ids=[folder_id],
permissions=[write_with_link_permission],
)['alternateLink']

# Create a Raw Photos folder.
task_data['raw_photo_folder'] = create_folder_with_permissions(
folder_id,
'Raw Photos',
permissions=[write_with_link_permission],
)['id']

return task_data
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
(function () {
'use strict';

angular
.module('journalism_workflow.copy_editor.controllers')
.controller('CopyEditorController', CopyEditorController)

CopyEditorController.$inject = ['$scope', 'orchestraService'];

function CopyEditorController($scope, orchestraService) {
var vm = $scope;

// Store the article text document URL for easier summary later
var documentCreationStep = orchestraService.taskUtils.findPrerequisite(
vm.taskAssignment, 'document_creation')
vm.taskAssignment.task.data.articleDocument = documentCreationStep.task.data.articleURL;

// Set up the photos for captioning
vm.taskAssignment.task.data.photos = [];
var photoAdjustStep = orchestraService.taskUtils.findPrerequisite(
vm.taskAssignment, 'photo_adjustment');
var photos = photoAdjustStep.task.data.photos_for_caption;
for (var i = 0; i < photos.length; i++) {
vm.taskAssignment.task.data.photos.push({
src: photos[i],
caption: ''
});
}
}
})();
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
(function () {
'use strict';

angular.module('journalism_workflow.copy_editor.directives').directive(
'copyEditor', function() {
return {
restrict: 'E',
controller: 'CopyEditorController',
scope: {
taskAssignment: '=',
},
templateUrl: '/static/journalism_workflow/copy_editor/partials/copy_editor.html',
};
});
})();
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
(function () {
'use strict';

angular.module('journalism_workflow.copy_editor.module', [
'journalism_workflow.copy_editor.controllers',
'journalism_workflow.copy_editor.directives'
]);

angular.module('journalism_workflow.copy_editor.controllers', []);
angular.module('journalism_workflow.copy_editor.directives', []);

})();
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<section class="section-panel">
<div class="container-fluid">
<div class="row section-header">
<div class="col-lg-12 col-md-12 col-sm-12">
<h3>
Finalize the article copy
</h3>
</div>
</div>
<div class="row padded">
<div class="col-lg-12 col-md-12 col-sm-12">

<!-- Headline -->
<div class="row padded">
<div class="col-lg-12 col-md-12 col-sm-12">
<h3>Write a headline for the article</h3>
</div>
</div>
<div class="row padded">
<div class="col-lg-12 col-md-12 col-sm-12">
<form class="form-horizontal text-right">
<div class="form-group">
<label for="headlineInput" class="col-sm-1 col-md-1 col-lg-1 control-label text-right">Headline</label>
<div class="col-sm-11 col-md-11 col-lg-11">
<input type="text" class="form-control" id="headlineInput" placeholder="Headline for the story" ng-model="taskAssignment.task.data.headline">
</div>
</div>
</form>
</div>
</div>

<!-- Photo Captions -->
<div class="row padded">
<div class="col-lg-12 col-md-12 col-sm-12">
<h3>Caption the photos taken for the article</h3>
</div>
</div>
<div class="row padded">
<div ng-repeat="img in taskAssignment.task.data.photos">
<div class="col-sm-6 col-md-4">
<div class="thumbnail">
<a href="{{img.src}}" target="_blank">
<img src="{{img.src}}" alt="Processed Image">
</a>
<div class="caption text-center">
<h3>Caption</h3>
<form class="form-horizontal">
<div class="form-group">
<div class="col-sm-12">
<input type="text" class="form-control" placeholder="Caption for the photo" ng-model="img.caption"/>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>

</div>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
(function () {
'use strict';

angular
.module('journalism_workflow.editor.controllers')
.controller('StoryFormController', StoryFormController)

StoryFormController.$inject = ['$scope', 'orchestraService'];

function StoryFormController($scope, orchestraService) {
var vm = $scope;
}
})();
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
(function () {
'use strict';

angular.module('journalism_workflow.editor.directives').directive(
'editor', function() {
return {
restrict: 'E',
controller: 'StoryFormController',
scope: {
taskAssignment: '=',
},
templateUrl: '/static/journalism_workflow/editor/partials/editor.html',
};
});
})();
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
(function () {
'use strict';

angular.module('journalism_workflow.editor.module', [
'journalism_workflow.editor.controllers',
'journalism_workflow.editor.directives'
]);

angular.module('journalism_workflow.editor.controllers', []);
angular.module('journalism_workflow.editor.directives', []);

})();

0 comments on commit 5741194

Please sign in to comment.