From 0d79c63a1942b226282ba95f88c084780b9aa8d5 Mon Sep 17 00:00:00 2001 From: Ryan Noelk Date: Sun, 29 Jan 2017 15:18:05 -0500 Subject: [PATCH 1/3] Dev to Master (#96) * adding debounce to react search (#69) * Make sure that the logged-in user is the author for the recipe (#76) * adding allowed host env * Make sure that the logged-in user is the author for the recipe * adding allowed host of env_stg * Fixing recipe sidebar links * updating docs to 1.10 * Displays response errors in recipe form (#72) * when reloading the recipe form it was using a list and not a dict (#77) * Removes edit() from RecipeActions (#75) * Allows access to `node` for docker support (#84) Using docker we need to allow access from the `node` container. * Replaces usage of instructions for directions (#85) * reordering ingredient list * Shows the notfound page if the recipe doesn't exist (#93) * adding german i18n (#94) --- api/base/settings.py | 8 +- api/v1/recipe/serializers.py | 8 +- env_stg.list | 1 + .../modules/browse/components/SearchBar.js | 17 +- frontend/modules/index.js | 3 +- frontend/modules/locale/de.json | 70 ++++++++ frontend/modules/locale/en.json | 10 +- frontend/modules/locale/es.json | 10 +- .../modules/recipe/components/Directions.js | 8 +- frontend/modules/recipe/components/Recipe.js | 32 ++-- frontend/modules/recipe/css/recipe.scss | 2 +- .../recipe_form/actions/RecipeActions.js | 49 +----- .../recipe_form/components/DirectionList.js | 93 ++++++---- .../recipe_form/components/FormComponents.js | 74 +++++++- .../recipe_form/components/IngredientList.js | 51 +++++- .../recipe_form/components/RecipeForm.js | 161 +++++++++++++++--- .../modules/recipe_form/components/TagList.js | 24 ++- .../modules/recipe_form/stores/RecipeStore.js | 9 +- frontend/package.json | 3 +- frontend/scripts/merge-locale.js | 2 +- 20 files changed, 469 insertions(+), 166 deletions(-) create mode 100644 frontend/modules/locale/de.json diff --git a/api/base/settings.py b/api/base/settings.py index 950b90ae..28abf3d3 100644 --- a/api/base/settings.py +++ b/api/base/settings.py @@ -28,8 +28,12 @@ } } # Hosts/domain names that are valid for this site; required if DEBUG is False -# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts -ALLOWED_HOSTS = ['localhost', '127.0.0.1'] +# See https://docs.djangoproject.com/en/1.10/ref/settings/#allowed-hosts +ALLOWED_HOSTS = ['localhost', '127.0.0.1', 'node'] + +env_allowed_host = os.environ.get('ALLOWED_HOST', None) +if env_allowed_host is not None: + ALLOWED_HOSTS.append(env_allowed_host) # List of callables that know how to import templates from various sources. TEMPLATES = [ diff --git a/api/v1/recipe/serializers.py b/api/v1/recipe/serializers.py index 891a476f..48f5d558 100644 --- a/api/v1/recipe/serializers.py +++ b/api/v1/recipe/serializers.py @@ -112,8 +112,12 @@ def create(self, validated_data): direction_data = validated_data.pop('directions', None) tag_data = validated_data.pop('tags', None) - # Create the recipe - recipe = Recipe.objects.create(**validated_data) + # Create the recipe. + # Use the log-in user as the author. + recipe = Recipe.objects.create( + author=self.context['request'].user, + **validated_data + ) # Create the Ingredients for ingredient in ingredient_data: diff --git a/env_stg.list b/env_stg.list index b17893d1..9aee9a38 100644 --- a/env_stg.list +++ b/env_stg.list @@ -9,6 +9,7 @@ DJANGO_SECERT_KEY=sdfsadfas32e98zsdvhhsnz6udvbksjdhfi4galshjfg DJANGO_TIME_ZONE=America/Chicago DJANGO_SETTINGS_MODULE=base.settings DJANGO_DEBUG=False +ALLOWED_HOST='' # Node config NODE_ENV=production diff --git a/frontend/modules/browse/components/SearchBar.js b/frontend/modules/browse/components/SearchBar.js index aec1c7ab..892000ad 100644 --- a/frontend/modules/browse/components/SearchBar.js +++ b/frontend/modules/browse/components/SearchBar.js @@ -1,4 +1,5 @@ import React from 'react' +import DebounceInput from 'react-debounce-input'; import { injectIntl, IntlProvider, @@ -38,13 +39,15 @@ export default injectIntl(React.createClass({
{ formatMessage(messages.search) }: - +
) diff --git a/frontend/modules/index.js b/frontend/modules/index.js index b08794e6..93944a6c 100644 --- a/frontend/modules/index.js +++ b/frontend/modules/index.js @@ -6,7 +6,8 @@ import { Router, Route, browserHistory, IndexRoute } from 'react-router' // Load default locale data; import en from 'react-intl/locale-data/en'; import es from 'react-intl/locale-data/es'; -addLocaleData([...en, ...es]); +import de from 'react-intl/locale-data/de'; +addLocaleData([...en, ...es, ...de]); const messages = require('./locale/'+process.env.LOCALE+'.json'); diff --git a/frontend/modules/locale/de.json b/frontend/modules/locale/de.json new file mode 100644 index 00000000..ccb26cb7 --- /dev/null +++ b/frontend/modules/locale/de.json @@ -0,0 +1,70 @@ +{ + "login.please_sign_in": "Bitte einloggen.", + "login.username": "Benutzername", + "login.password": "Passwort", + "login.sign_in": "Einloggen", + "login.alert.unable_to_login": "Das Einloggen war nicht erfolgreich!", + "login.alert.confirm": "Bitte stellen Sie sicher, dass ihr Benutzername und Ihr Passwort richtig sind.", + "404.header": "Unsere Köche haben das Rezept in der Testküche versaut, bitte versuchen Sie etwas anderes.", + "404.message": "Entschuldigung! Ein 404 Fehler ist aufgetreten, wir können nicht finden wonach Sie suchen.", + "nav.brand": "Open Eats", + "nav.news": "News", + "nav.recipes": "Rezept", + "nav.about": "Über", + "nav.login.title": "Login", + "nav.accountmenu.title": "Mein Account", + "nav.accountmenu.create_recipe": "Rezept erstellen.", + "nav.accountmenu.logout": "Ausloggen", + "browse.no_results": "Leider gibt es zu dieser Anfrage keine Ergebnisse.", + "filter.filter_x": "Filter {title}", + "filter.clear_filter": "Filter leeren", + "pagination.newer": "Neuere", + "pagination.older": "Ältere", + "searchbar.label": "Suche nach Rezepten", + "searchbar.placeholder": "Geben Sie einen Begriff oder eine Zutat ein.", + "autocomplete.loading": "Laden...", + "autocomplete.no_matches": "Keine Übereinstimmung mit {value} gefunden.", + "recipe.create.name_label": "Rezepttitel", + "recipe.create.name_placeholder": "Rezept", + "recipe.create.course_label": "Gang", + "recipe.create.cuisine_label": "Küche", + "recipe.create.tags_label": "Tags", + "recipe.create.prep_time_label": "Zubereitungszeit", + "recipe.create.prep_time_placeholder": "Zubereitungszeit in Minuten", + "recipe.create.cooking_time_label": "Kochzeit", + "recipe.create.cooking_time_placeholder": "Kochzeit in Minuten", + "recipe.create.servings_label": "Portionen", + "recipe.create.servings_placeholder": "Portionen", + "recipe.create.rating_label": "Bewertung", + "recipe.create.rating_placeholder": "Bewerten Sie dieses Rezept mit einer Zahl zwischen 0 und 5", + "recipe.create.ingredients_label": "Zutaten", + "recipe.create.instructions_label": "Anweisungen", + "recipe.create.information_label": "Rezeptinformation", + "recipe.create.information_placeholder": "Kurze Beschreibung des Rezepts", + "recipe.create.source_label": "Quelle", + "recipe.create.source_placeholder": "URL Quelle des Rezepts (falls existent)", + "recipe.create.photo_label": "Photo", + "recipe.create.photo_placeholder": "Photo", + "recipe.create.submit": "Rezept anlegen", + "instructions.step_label": "Schritt {step, number}", + "instructions.textarea_placeholder": "Anweisung", + "instructions.next_instruction": "Nächste Anweisung", + "file_widget.help_block": "Bitte laden Sie ein Bild des fertigen Gerichts hoch!", + "select_widget.header": "Bitte wählen Sie ein {label}", + "widget_error.title": "Formularfehler!", + "widget_error.message": "Bitte füllen Sie alle benötigten Felder aus.", + "ingredients.name_label": "Bezeichnung", + "ingredients.name_placeholder": "Bezeichnung", + "ingredients.quantity_label": "Menge", + "ingredients.quantity_placeholder": "Menge", + "ingredients.measurement_label": "Einheit", + "ingredients.measurement_placeholder": "Einheit", + "ingredients.add_ingredient": "Zutat hinzufügen", + "recipe.cooking_time": "Kochzeit", + "recipe.prep_time": "Vorbereitungszeit", + "recipe.servings": "Portionen", + "recipe.ingredients": "Zutaten", + "recipe.instructions": "Anleitung", + "recipe.photo_placeholder": "Generisches Platzhalter-Thumbnail", + "recipe.edit_recipe": "Rezept ändern" +} diff --git a/frontend/modules/locale/en.json b/frontend/modules/locale/en.json index 01057a98..ae30f8e9 100644 --- a/frontend/modules/locale/en.json +++ b/frontend/modules/locale/en.json @@ -38,7 +38,7 @@ "recipe.create.rating_label": "Rating", "recipe.create.rating_placeholder": "Rate this recipe from 0 to 5", "recipe.create.ingredients_label": "Ingredients", - "recipe.create.instructions_label": "Instructions", + "recipe.create.directions_label": "Directions", "recipe.create.information_label": "Recipe information", "recipe.create.information_placeholder": "A quick description of the recipe", "recipe.create.source_label": "Source", @@ -46,9 +46,9 @@ "recipe.create.photo_label": "Photo", "recipe.create.photo_placeholder": "Photo", "recipe.create.submit": "Submit recipe", - "instructions.step_label": "Step {step, number}", - "instructions.textarea_placeholder": "Instruction", - "instructions.next_instruction": "Next instruction", + "directions.step_label": "Step {step, number}", + "directions.textarea_placeholder": "Direction", + "directions.next_direction": "Next direction", "file_widget.help_block": "Please upload a picture of the finished recipe!", "select_widget.header": "Please select a {label}", "widget_error.title": "Form error!", @@ -64,7 +64,7 @@ "recipe.prep_time": "Prep time", "recipe.servings": "Servings", "recipe.ingredients": "Ingredients", - "recipe.instructions": "Instructions", + "recipe.directions": "Directions", "recipe.photo_placeholder": "Generic placeholder thumbnail", "recipe.edit_recipe": "Edit recipe" } \ No newline at end of file diff --git a/frontend/modules/locale/es.json b/frontend/modules/locale/es.json index 88a0ad5e..801d0a7f 100644 --- a/frontend/modules/locale/es.json +++ b/frontend/modules/locale/es.json @@ -38,7 +38,7 @@ "recipe.create.rating_label": "Rating", "recipe.create.rating_placeholder": "Dele una valoración del 0 a 5", "recipe.create.ingredients_label": "Ingredientes", - "recipe.create.instructions_label": "Instrucciones", + "recipe.create.directions_label": "Instrucciones", "recipe.create.information_label": "Información", "recipe.create.information_placeholder": "Una descripción breve de la receta", "recipe.create.source_label": "Fuente", @@ -46,9 +46,9 @@ "recipe.create.photo_label": "Foto", "recipe.create.photo_placeholder": "Foto", "recipe.create.submit": "Guardar receta", - "instructions.step_label": "Paso {step, number}", - "instructions.textarea_placeholder": "Instrucción", - "instructions.next_instruction": "Próxima instrucción", + "directions.step_label": "Paso {step, number}", + "directions.textarea_placeholder": "Instrucción", + "directions.next_direction": "Próxima instrucción", "file_widget.help_block": "¡Añada una foto del plato completado!", "select_widget.header": "Elija un {label}", "widget_error.title": "¡Error!", @@ -64,7 +64,7 @@ "recipe.prep_time": "Tiempo de preparación", "recipe.servings": "Sirve", "recipe.ingredients": "Ingredientes", - "recipe.instructions": "Instrucciones", + "recipe.directions": "Instrucciones", "recipe.photo_placeholder": "Foto generica", "recipe.edit_recipe": "Modificar receta" } \ No newline at end of file diff --git a/frontend/modules/recipe/components/Directions.js b/frontend/modules/recipe/components/Directions.js index 6837778c..1cf10e29 100644 --- a/frontend/modules/recipe/components/Directions.js +++ b/frontend/modules/recipe/components/Directions.js @@ -24,16 +24,16 @@ export default React.createClass({ this.loadRecipeFromServer(); }, render: function() { - var ingredients = this.state.data.map(function(direction) { + var directions = this.state.data.map(function(direction) { return ( -
  • +
  • { direction.title }
  • ); }); return ( -
      - { ingredients } +
        + { directions }
      ); } diff --git a/frontend/modules/recipe/components/Recipe.js b/frontend/modules/recipe/components/Recipe.js index 7a931eed..c562f6cb 100644 --- a/frontend/modules/recipe/components/Recipe.js +++ b/frontend/modules/recipe/components/Recipe.js @@ -1,12 +1,12 @@ import React from 'react' -import { Link } from 'react-router' -import request from 'superagent'; +import { Link, browserHistory } from 'react-router' +import request from 'superagent' import { injectIntl, IntlProvider, defineMessages, formatMessage -} from 'react-intl'; +} from 'react-intl' import AuthStore from '../../account/stores/AuthStore' import MiniBrowse from '../../browse/components/MiniBrowse' @@ -18,8 +18,8 @@ import Ratings from './Ratings' require("./../css/recipe.scss"); export default React.createClass({ - loadRecipeFromServer: function() { - var url = serverURLs.recipe + this.props.params.recipe + "/?format=json"; + loadRecipeFromServer: function(id) { + var url = serverURLs.recipe + id + "/?format=json"; request .get(url) .type('json') @@ -27,7 +27,11 @@ export default React.createClass({ if (!err && res) { this.setState({ data: res.body }); } else { - console.error(url, err.toString()); + if (res.statusCode == 404) { + browserHistory.replace('/notfound'); + } else { + console.error(url, err.toString()); + } } }) }, @@ -41,13 +45,17 @@ export default React.createClass({ componentDidMount: function() { AuthStore.addChangeListener(this._onChange); - this.loadRecipeFromServer(); + this.loadRecipeFromServer(this.props.params.recipe); }, componentWillUnmount: function() { AuthStore.removeChangeListener(this._onChange); }, + componentWillReceiveProps(nextProps) { + this.loadRecipeFromServer(nextProps.params.recipe); + }, + _onChange: function() { this.setState({user: this.getAuthUser()}); }, @@ -97,10 +105,10 @@ var RecipeScheme = injectIntl(React.createClass({ description: 'Ingredients', defaultMessage: 'Ingredients', }, - instructions: { - id: 'recipe.instructions', - description: 'Instructions', - defaultMessage: 'Instructions', + directions: { + id: 'recipe.directions', + description: 'Directions', + defaultMessage: 'Directions', }, source: { id: 'recipe.source', @@ -183,7 +191,7 @@ var RecipeScheme = injectIntl(React.createClass({
      -

      { formatMessage(messages.instructions) }

      +

      { formatMessage(messages.directions) }

      diff --git a/frontend/modules/recipe/css/recipe.scss b/frontend/modules/recipe/css/recipe.scss index 5e8def4f..5a7543ed 100644 --- a/frontend/modules/recipe/css/recipe.scss +++ b/frontend/modules/recipe/css/recipe.scss @@ -50,7 +50,7 @@ height: 0; line-height: 0; } - .instructions, .ingredients { + .directions, .ingredients { display: block; } } diff --git a/frontend/modules/recipe_form/actions/RecipeActions.js b/frontend/modules/recipe_form/actions/RecipeActions.js index 6bf23540..e771666e 100644 --- a/frontend/modules/recipe_form/actions/RecipeActions.js +++ b/frontend/modules/recipe_form/actions/RecipeActions.js @@ -5,7 +5,6 @@ import RecipeConstants from '../constants/RecipeConstants'; import {serverURLs} from '../../common/config' class RecipeActions { - /* TODO: merge submit() and edit() */ submit(data) { let photo = false; if (data.photo){ @@ -15,51 +14,11 @@ class RecipeActions { delete data['photo']; delete data['photo_thumbnail']; - request - .post(serverURLs.recipe) - .send(data) - .set('Authorization', 'Token ' + AuthStore.getToken()) - .end((err, res) => { - if (!err && res) { - //send the image once the file has been created - var id = res.body.id; - if (photo) { - request - .patch(serverURLs.recipe + id + "/") - .attach('photo', photo) - .set('Authorization', 'Token ' + AuthStore.getToken()) - .end((err, res) => { - if (!err && res) { - this.handleSubmit(res.body.id); - } else { - console.error(serverURLs.recipe, err.toString()); - console.error(res.body); - this.error(res.body); - } - }); - } else { - this.handleSubmit(res.body.id); - } - } else { - console.error(serverURLs.recipe, err.toString()); - console.error(res.body); - this.error(res.body); - } - }); - } + let r = 'id' in data ? + request.put(serverURLs.recipe + data.id + '/') : + request.post(serverURLs.recipe) ; - edit(data) { - let photo = false; - if (data.photo){ - photo = data.photo; - } - - delete data['photo']; - delete data['photo_thumbnail']; - - request - .put(serverURLs.recipe + data['id'] + '/') - .send(data) + r.send(data) .set('Authorization', 'Token ' + AuthStore.getToken()) .end((err, res) => { if (!err && res) { diff --git a/frontend/modules/recipe_form/components/DirectionList.js b/frontend/modules/recipe_form/components/DirectionList.js index 2a789899..3cf09b92 100644 --- a/frontend/modules/recipe_form/components/DirectionList.js +++ b/frontend/modules/recipe_form/components/DirectionList.js @@ -7,7 +7,7 @@ import { } from 'react-intl'; import { TextArea } from './FormComponents' -var Instruction = injectIntl(React.createClass({ +var Direction = injectIntl(React.createClass({ getInitialState: function() { return { step: this.props.data ? this.props.data.step : 0, @@ -26,24 +26,24 @@ var Instruction = injectIntl(React.createClass({ const {formatMessage} = this.props.intl; const messages = defineMessages({ step_label: { - id: 'instructions.step_label', + id: 'directions.step_label', description: 'Step # label', defaultMessage: 'Step {step, number}', }, textarea_placeholder: { - id: 'instructions.textarea_placeholder', + id: 'directions.textarea_placeholder', description: 'Step # label', - defaultMessage: 'Instruction', + defaultMessage: 'Direction', } }); return ( -
      +