# HG changeset patch # User ymh # Date 1498126188 -7200 # Node ID 043477fd5c5c3d8e529c4778dba618d6a922303c # Parent 7e8cdc74d86f648a427495cb04c872a436ad391d add api call to save notes. internally use ts for time data for notes and session diff -r 7e8cdc74d86f -r 043477fd5c5c client/src/actions/notesActions.js --- a/client/src/actions/notesActions.js Thu Jun 22 11:58:27 2017 +0200 +++ b/client/src/actions/notesActions.js Thu Jun 22 12:09:48 2017 +0200 @@ -3,17 +3,43 @@ import * as types from '../constants/actionTypes'; export const addNote = (session, data) => { + const noteId = uuidV1(); + const note = { + _id: noteId, + session: session._id, + raw: data.raw, + plain: data.plain, + html: data.html, + startedAt: data.startedAt, + finishedAt: data.finishedAt, + categories: data.categories, + }; + + const noteSrvr = { + ext_id: noteId, + session: session._id, + raw: JSON.stringify(data.raw), + plain: data.plain, + html: data.html, + tc_start: data.startedAt, + tc_end: data.finishedAt, + categorization: JSON.stringify(data.categories), + } + return { type: types.ADD_NOTE, - note: { - _id: uuidV1(), - session: session._id, - raw: data.raw, - plain: data.plain, - html: data.html, - startedAt: data.startedAt, - finishedAt: data.finishedAt, - categories: data.categories, + note, + meta: { + offline: { + effect: { + url: `/api/notes/sessions/${session.get('_id')}/notes/`, + method: 'POST', + data: noteSrvr + }, + commit: { type: types.NOOP }, + rollback: { type: types.NOOP } + } } + }; } diff -r 7e8cdc74d86f -r 043477fd5c5c client/src/actions/sessionsActions.js --- a/client/src/actions/sessionsActions.js Thu Jun 22 11:58:27 2017 +0200 +++ b/client/src/actions/sessionsActions.js Thu Jun 22 12:09:48 2017 +0200 @@ -1,14 +1,12 @@ -//import uuidV1 from 'uuid/v1'; - +import { now } from '../utils'; import * as types from '../constants/actionTypes'; export const createSession = (sessionId) => { - //const sessionId = uuidV1(); const newSession = { _id: sessionId, ext_id: sessionId, - date: new Date(), + date: now(), title: '', description: '', }; @@ -35,6 +33,17 @@ type: types.UPDATE_SESSION, session: session, values: values, + meta: { + offline: { + effect: { + url: `/api/notes/sessions/${session.get('_id')}/`, + method: 'PUT', + data: values + }, + commit: { type: types.NOOP }, + rollback: { type: types.NOOP } + } + } }; } diff -r 7e8cdc74d86f -r 043477fd5c5c client/src/components/Note.js --- a/client/src/components/Note.js Thu Jun 22 11:58:27 2017 +0200 +++ b/client/src/components/Note.js Thu Jun 22 12:09:48 2017 +0200 @@ -1,11 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; +import {formatTimestamp} from '../utils'; const Note = ({note}) => { return (
- {note.startedAt} - {note.finishedAt} + {formatTimestamp(note.startedAt)} + {formatTimestamp(note.finishedAt)}
); diff -r 7e8cdc74d86f -r 043477fd5c5c client/src/components/NoteInput.js --- a/client/src/components/NoteInput.js Thu Jun 22 11:58:27 2017 +0200 +++ b/client/src/components/NoteInput.js Thu Jun 22 12:09:48 2017 +0200 @@ -1,13 +1,14 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; -import { Form, FormControl, Label } from 'react-bootstrap'; -import moment from 'moment'; +import { Form, FormControl, Button } from 'react-bootstrap'; import PropTypes from 'prop-types'; import SlateEditor from './SlateEditor'; import * as notesActions from '../actions/notesActions'; import * as userActions from '../actions/userActions'; +import { now } from '../utils'; + class NoteInput extends Component { @@ -38,7 +39,7 @@ html: html, startedAt: this.state.startedAt, - finishedAt: moment().format('H:mm:ss'), + finishedAt: now(), categories: categories, }); @@ -55,20 +56,6 @@ this.setState({ buttonDisabled: text.length === 0 }); } - renderTiming() { - if (this.state.startedAt && this.state.finishedAt) { - return ( - -  ⇒  - - ) - } else { - return ( - No timing - ) - } - } - render() { return (
diff -r 7e8cdc74d86f -r 043477fd5c5c client/src/components/SessionSummary.js --- a/client/src/components/SessionSummary.js Thu Jun 22 11:58:27 2017 +0200 +++ b/client/src/components/SessionSummary.js Thu Jun 22 12:09:48 2017 +0200 @@ -3,6 +3,7 @@ import { Panel, ListGroup, ListGroupItem } from 'react-bootstrap'; import _ from 'lodash'; import '../App.css'; +import {formatTimestamp} from '../utils'; class SessionSummary extends Component { render() { @@ -11,7 +12,7 @@ {this.props.notes.map((note) => - {note.startedAt} → {note.finishedAt} + {formatTimestamp(note.startedAt)} → {formatTimestamp(note.finishedAt)} {_.words(note.plain).length} words diff -r 7e8cdc74d86f -r 043477fd5c5c client/src/components/SlateEditor.js --- a/client/src/components/SlateEditor.js Thu Jun 22 11:58:27 2017 +0200 +++ b/client/src/components/SlateEditor.js Thu Jun 22 12:09:48 2017 +0200 @@ -2,11 +2,11 @@ import React from 'react' import Portal from 'react-portal' import { Button } from 'react-bootstrap' -import moment from 'moment' import Immutable from 'immutable' import HtmlSerializer from '../HtmlSerializer' import AnnotationPlugin from '../AnnotationPlugin' import CategoriesTooltip from './CategoriesTooltip' +import { now } from '../utils'; const plugins = []; @@ -143,12 +143,12 @@ finishedAt: null }); } else { - Object.assign(newState, { finishedAt: moment().format('H:mm:ss') }); + Object.assign(newState, { finishedAt: now() }); } // Store start time once when the first character is typed if (!isEmpty && this.state.startedAt === null) { - Object.assign(newState, { startedAt: moment().format('H:mm:ss') }); + Object.assign(newState, { startedAt: now() }); } this.setState(newState) diff -r 7e8cdc74d86f -r 043477fd5c5c client/src/store/configureStore.js --- a/client/src/store/configureStore.js Thu Jun 22 11:58:27 2017 +0200 +++ b/client/src/store/configureStore.js Thu Jun 22 12:09:48 2017 +0200 @@ -52,7 +52,7 @@ const offlineConfig = { ...offlineDefaultConfig, persistOptions, - effect: createEffect(apiClient) + effect: createEffect(apiClient), // detectNetwork: callback => callback(true), } diff -r 7e8cdc74d86f -r 043477fd5c5c client/src/utils.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/src/utils.js Thu Jun 22 12:09:48 2017 +0200 @@ -0,0 +1,11 @@ +import moment from 'moment' + +export const defaultDateTimeformat = "YYYY-MM-DDTHH:mm:ss.SSSZ"; + +export const formatTimestamp = (ts) => { + return moment(ts).local().format('H:mm:ss'); +} + +export const now = () => { + return moment.utc().format(defaultDateTimeformat); +} diff -r 7e8cdc74d86f -r 043477fd5c5c src/notes/api/serializers/core.py --- a/src/notes/api/serializers/core.py Thu Jun 22 11:58:27 2017 +0200 +++ b/src/notes/api/serializers/core.py Thu Jun 22 12:09:48 2017 +0200 @@ -1,9 +1,12 @@ """ Serializers for model core classes """ +import logging + +from notes.models import Note, Session from rest_framework import serializers -from notes.models import Note, Session +logger = logging.getLogger(__name__) class DetailNoteSerializer(serializers.ModelSerializer): @@ -16,6 +19,22 @@ ) read_only_fields = ('ext_id', 'version', 'created', 'updated') +class CreateNoteSerializer(serializers.ModelSerializer): + class Meta: + model = Note + fields = ( + 'ext_id', 'version', 'created', 'updated', + 'plain', 'html', 'raw', + 'categorization', 'margin_note', 'tc_start', 'tc_end' + ) + read_only_fields = ('version', 'created', 'updated') + + def to_internal_value(self, data): + super_data = super().to_internal_value(data) + super_data['session'] = Session.objects.get( + ext_id=self.context['view'].kwargs['session_ext_id'] + ) + return super_data class ListNoteSerializer(serializers.ModelSerializer): class Meta: diff -r 7e8cdc74d86f -r 043477fd5c5c src/notes/api/views/core.py --- a/src/notes/api/views/core.py Thu Jun 22 11:58:27 2017 +0200 +++ b/src/notes/api/views/core.py Thu Jun 22 12:09:48 2017 +0200 @@ -4,7 +4,7 @@ from rest_framework import viewsets from ..permissions import NotePermission, SessionPermission -from ..serializers.core import (DetailNoteSerializer, DetailSessionSerializer, +from ..serializers.core import (DetailNoteSerializer, DetailSessionSerializer, CreateNoteSerializer, ListNoteSerializer, ListSessionSerializer, CreateSessionSerializer) logger = logging.getLogger(__name__) @@ -37,7 +37,7 @@ serializers = { 'list': ListNoteSerializer, 'retrieve': DetailNoteSerializer, - 'create': DetailNoteSerializer, + 'create': CreateNoteSerializer, 'update': DetailNoteSerializer, } lookup_field = 'ext_id' diff -r 7e8cdc74d86f -r 043477fd5c5c src/notes/tests/__init__.py --- a/src/notes/tests/__init__.py Thu Jun 22 11:58:27 2017 +0200 +++ b/src/notes/tests/__init__.py Thu Jun 22 12:09:48 2017 +0200 @@ -1,1 +1,1 @@ -from .api import SessionApiTests +from .api import SessionApiTests, NoteApiTests diff -r 7e8cdc74d86f -r 043477fd5c5c src/notes/tests/api/__init__.py --- a/src/notes/tests/api/__init__.py Thu Jun 22 11:58:27 2017 +0200 +++ b/src/notes/tests/api/__init__.py Thu Jun 22 12:09:48 2017 +0200 @@ -1,3 +1,4 @@ from .session import SessionApiTests +from.note import NoteApiTests -__all__ = 'SessionApiTests' +__all__ = ['SessionApiTests', 'NoteApiTests'] diff -r 7e8cdc74d86f -r 043477fd5c5c src/notes/tests/api/note.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/notes/tests/api/note.py Thu Jun 22 12:09:48 2017 +0200 @@ -0,0 +1,135 @@ +""" +Tests the core api for sessions +""" +import logging +from uuid import uuid4 + +from django.contrib.auth import get_user_model +from django.urls import reverse +from django.utils import timezone +from rest_framework import status +from rest_framework.test import APITransactionTestCase + +from notes.models import Session, Note + +logger = logging.getLogger(__name__) + +class NoteApiTests(APITransactionTestCase): + + def setUp(self): + User = get_user_model() + user1 = User.objects.create_user( + username='test_user1', + email='test_user@emial.com', + password='top_secret' + ) + user2 = User.objects.create_user( + username='test_user2', + email='test_user@emial.com', + password='top_secret' + ) + user3 = User.objects.create_user( + username='test_user3', + email='test_user@emial.com', + password='top_secret' + ) + + self.session1 = Session.objects.create( + title="a new session 1", + description="Description 1", + protocol="[]", + owner=user1 + ) + + self.session2 = Session.objects.create( + title="a new session 2", + description="Description 2", + protocol="[]", + owner=user2 + ) + + Session.objects.create( + title="a new session 3", + description="Description 3", + protocol="[]", + owner=user3 + ) + + Note.objects.create( + tc_start=timezone.now(), + tc_end=timezone.now(), + session=self.session1, + plain="example note 1", + html="example note 1", + raw="example note 1", + margin_note="margin note 1", + categorization="[]" + ) + + Note.objects.create( + tc_start=timezone.now(), + tc_end=timezone.now(), + session=self.session2, + plain="example note 2", + html="example note", + raw="example note", + margin_note="margin note", + categorization="[]" + ) + + + def test_create_note_no_user(self): + url = reverse('notes:notes-list', kwargs={'session_ext_id': self.session1.ext_id}) + response = self.client.post(url, { + 'tc_start': timezone.now(), + 'tc_end': timezone.now(), + 'plain': "example note 2", + 'html': "example note", + 'raw': "example note", + 'margin_note': "margin note", + 'categorization': "[]" + }, format='json') + + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + + def test_create_note(self): + url = reverse('notes:notes-list', kwargs={'session_ext_id': self.session1.ext_id}) + self.client.login(username='test_user1', password='top_secret') + response = self.client.post(url, { + 'tc_start': timezone.now(), + 'tc_end': timezone.now(), + 'plain': "example note 2", + 'html': "example note", + 'raw': "example note", + 'margin_note': "margin note", + 'categorization': "[]" + }, format='json') + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + json = response.json() + self.assertIn('ext_id', json) + note = Note.objects.get(ext_id=json['ext_id'], session__id=self.session1.id) + self.assertTrue(note) + + def test_create_note_with_ext_id(self): + url = reverse('notes:notes-list', kwargs={'session_ext_id': self.session1.ext_id}) + self.client.login(username='test_user1', password='top_secret') + ext_id = str(uuid4()) + response = self.client.post(url, { + 'ext_id': ext_id, + 'tc_start': timezone.now(), + 'tc_end': timezone.now(), + 'plain': "example note 2", + 'html': "example note", + 'raw': "example note", + 'margin_note': "margin note", + 'categorization': "[]" + }, format='json') + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + json = response.json() + self.assertIn('ext_id', json) + self.assertEqual(json['ext_id'], ext_id) + note = Note.objects.get(ext_id=ext_id) + self.assertTrue(note) diff -r 7e8cdc74d86f -r 043477fd5c5c src/notes/tests/api/session.py --- a/src/notes/tests/api/session.py Thu Jun 22 11:58:27 2017 +0200 +++ b/src/notes/tests/api/session.py Thu Jun 22 12:09:48 2017 +0200 @@ -2,6 +2,7 @@ Tests the core api for sessions """ import logging +from uuid import uuid4 from django.contrib.auth import get_user_model from django.urls import reverse @@ -78,13 +79,13 @@ def test_list_session_no_user(self): - url = reverse('notes_api:session-list') + url = reverse('notes:session-list') response = self.client.post(url) - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) def test_list_session(self): - url = reverse('notes_api:session-list') + url = reverse('notes:session-list') self.client.login(username='test_user1', password='top_secret') response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -95,18 +96,18 @@ def test_create_session_no_user(self): - url = reverse('notes_api:session-list') + url = reverse('notes:session-list') response = self.client.post(url, { 'title': "a new session", 'description': "description of the session", 'protocol': "[]" }, format='json') - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) def test_create_session(self): - url = reverse('notes_api:session-list') + url = reverse('notes:session-list') self.client.login(username='test_user1', password='top_secret') response = self.client.post(url, { 'title': "a new session", @@ -119,26 +120,43 @@ json = response.json() self.assertIn('ext_id', json) + def test_create_session_with_ext_id(self): + url = reverse('notes:session-list') + self.client.login(username='test_user1', password='top_secret') + ext_id = str(uuid4()) + response = self.client.post(url, { + 'ext_id': ext_id, + 'title': "a new session", + 'description': "description of the session", + 'protocol': "[]" + }, format='json') + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + json = response.json() + self.assertIn('ext_id', json) + self.assertEqual(json['ext_id'], ext_id) + + def test_detail_session(self): - url = reverse('notes_api:session-detail', kwargs={'ext_id':str(self.session1.ext_id)}) + url = reverse('notes:session-detail', kwargs={'ext_id':str(self.session1.ext_id)}) self.client.login(username='test_user1', password='top_secret') response = self.client.get(url, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) def test_list_notes(self): - url = reverse('notes_api:notes-list', kwargs={'session_ext_id':str(self.session1.ext_id)}) + url = reverse('notes:notes-list', kwargs={'session_ext_id':str(self.session1.ext_id)}) self.client.login(username='test_user1', password='top_secret') response = self.client.get(url, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) def test_detail_session_bad(self): - url = reverse('notes_api:session-detail', kwargs={'ext_id':str(self.session2.ext_id)}) + url = reverse('notes:session-detail', kwargs={'ext_id':str(self.session2.ext_id)}) self.client.login(username='test_user1', password='top_secret') response = self.client.get(url, format='json') self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) def test_list_notes_bad(self): - url = reverse('notes_api:notes-list', kwargs={'session_ext_id':str(self.session2.ext_id)}) + url = reverse('notes:notes-list', kwargs={'session_ext_id':str(self.session2.ext_id)}) logger.debug("URL: %s", url) self.client.login(username='test_user1', password='top_secret') response = self.client.get(url, format='json')