API & Client: add user langage preferences in database
This commit is contained in:
		@@ -317,6 +317,7 @@
 | 
			
		||||
    <span class="nt">"email"</span><span class="p">:</span> <span class="s2">"sam@example.com"</span><span class="p">,</span>
 | 
			
		||||
    <span class="nt">"first_name"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
 | 
			
		||||
    <span class="nt">"id"</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
 | 
			
		||||
    <span class="nt">"language"</span><span class="p">:</span> <span class="s2">"en"</span><span class="p">,</span>
 | 
			
		||||
    <span class="nt">"last_name"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
 | 
			
		||||
    <span class="nt">"location"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
 | 
			
		||||
    <span class="nt">"nb_activities"</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
 | 
			
		||||
@@ -374,6 +375,7 @@
 | 
			
		||||
    <span class="nt">"email"</span><span class="p">:</span> <span class="s2">"sam@example.com"</span><span class="p">,</span>
 | 
			
		||||
    <span class="nt">"first_name"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
 | 
			
		||||
    <span class="nt">"id"</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
 | 
			
		||||
    <span class="nt">"language"</span><span class="p">:</span> <span class="s2">"en"</span><span class="p">,</span>
 | 
			
		||||
    <span class="nt">"last_name"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
 | 
			
		||||
    <span class="nt">"location"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
 | 
			
		||||
    <span class="nt">"nb_activities"</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
 | 
			
		||||
@@ -402,6 +404,7 @@
 | 
			
		||||
<li><p><strong>password_conf</strong> (<em>string</em>) – user password confirmation</p></li>
 | 
			
		||||
<li><p><strong>timezone</strong> (<em>string</em>) – user time zone</p></li>
 | 
			
		||||
<li><p><strong>weekm</strong> (<em>string</em>) – does week start on Monday?</p></li>
 | 
			
		||||
<li><p><strong>language</strong> (<em>string</em>) – language preferences</p></li>
 | 
			
		||||
</ul>
 | 
			
		||||
</dd>
 | 
			
		||||
<dt class="field-even">Request Headers</dt>
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -65,6 +65,7 @@ def user_1_full():
 | 
			
		||||
    user.last_name = 'Doe'
 | 
			
		||||
    user.bio = 'just a random guy'
 | 
			
		||||
    user.location = 'somewhere'
 | 
			
		||||
    user.language = 'en'
 | 
			
		||||
    user.timezone = 'America/New_York'
 | 
			
		||||
    user.birth_date = datetime.datetime.strptime('01/01/1980', '%d/%m/%Y')
 | 
			
		||||
    db.session.add(user)
 | 
			
		||||
 
 | 
			
		||||
@@ -432,6 +432,7 @@ def test_user_profile_minimal(app, user_1):
 | 
			
		||||
    assert not data['data']['admin']
 | 
			
		||||
    assert data['data']['timezone'] is None
 | 
			
		||||
    assert data['data']['weekm'] is False
 | 
			
		||||
    assert data['data']['language'] is None
 | 
			
		||||
    assert data['data']['nb_activities'] == 0
 | 
			
		||||
    assert data['data']['nb_sports'] == 0
 | 
			
		||||
    assert data['data']['total_distance'] == 0
 | 
			
		||||
@@ -467,6 +468,7 @@ def test_user_profile_full(app, user_1_full):
 | 
			
		||||
    assert data['data']['location'] == 'somewhere'
 | 
			
		||||
    assert data['data']['timezone'] == 'America/New_York'
 | 
			
		||||
    assert data['data']['weekm'] is False
 | 
			
		||||
    assert data['data']['language'] == 'en'
 | 
			
		||||
    assert data['data']['nb_activities'] == 0
 | 
			
		||||
    assert data['data']['nb_sports'] == 0
 | 
			
		||||
    assert data['data']['total_distance'] == 0
 | 
			
		||||
@@ -542,6 +544,7 @@ def test_user_profile_valid_update(app, user_1):
 | 
			
		||||
                password_conf='87654321',
 | 
			
		||||
                timezone='America/New_York',
 | 
			
		||||
                weekm=True,
 | 
			
		||||
                language='fr',
 | 
			
		||||
            )
 | 
			
		||||
        ),
 | 
			
		||||
        headers=dict(
 | 
			
		||||
@@ -564,6 +567,7 @@ def test_user_profile_valid_update(app, user_1):
 | 
			
		||||
    assert data['data']['location'] == 'Somewhere'
 | 
			
		||||
    assert data['data']['timezone'] == 'America/New_York'
 | 
			
		||||
    assert data['data']['weekm'] is True
 | 
			
		||||
    assert data['data']['language'] == 'fr'
 | 
			
		||||
    assert data['data']['nb_activities'] == 0
 | 
			
		||||
    assert data['data']['nb_sports'] == 0
 | 
			
		||||
    assert data['data']['total_distance'] == 0
 | 
			
		||||
@@ -589,6 +593,7 @@ def test_user_profile_valid_update_without_password(app, user_1):
 | 
			
		||||
                birth_date='1980-01-01',
 | 
			
		||||
                timezone='America/New_York',
 | 
			
		||||
                weekm=True,
 | 
			
		||||
                language='fr',
 | 
			
		||||
            )
 | 
			
		||||
        ),
 | 
			
		||||
        headers=dict(
 | 
			
		||||
@@ -611,6 +616,7 @@ def test_user_profile_valid_update_without_password(app, user_1):
 | 
			
		||||
    assert data['data']['location'] == 'Somewhere'
 | 
			
		||||
    assert data['data']['timezone'] == 'America/New_York'
 | 
			
		||||
    assert data['data']['weekm'] is True
 | 
			
		||||
    assert data['data']['language'] == 'fr'
 | 
			
		||||
    assert data['data']['nb_activities'] == 0
 | 
			
		||||
    assert data['data']['nb_sports'] == 0
 | 
			
		||||
    assert data['data']['total_distance'] == 0
 | 
			
		||||
@@ -682,6 +688,7 @@ def test_user_profile_invalid_password(app, user_1):
 | 
			
		||||
                password_conf='876543210',
 | 
			
		||||
                timezone='America/New_York',
 | 
			
		||||
                weekm=True,
 | 
			
		||||
                language='en',
 | 
			
		||||
            )
 | 
			
		||||
        ),
 | 
			
		||||
        headers=dict(
 | 
			
		||||
@@ -717,6 +724,7 @@ def test_user_profile_missing_password_conf(app, user_1):
 | 
			
		||||
                password='87654321',
 | 
			
		||||
                timezone='America/New_York',
 | 
			
		||||
                weekm=True,
 | 
			
		||||
                language='en',
 | 
			
		||||
            )
 | 
			
		||||
        ),
 | 
			
		||||
        headers=dict(
 | 
			
		||||
 
 | 
			
		||||
@@ -45,6 +45,7 @@ def test_single_user(app, user_1):
 | 
			
		||||
    assert data['data']['location'] is None
 | 
			
		||||
    assert data['data']['timezone'] is None
 | 
			
		||||
    assert data['data']['weekm'] is False
 | 
			
		||||
    assert data['data']['language'] is None
 | 
			
		||||
    assert data['data']['nb_activities'] == 0
 | 
			
		||||
    assert data['data']['nb_sports'] == 0
 | 
			
		||||
    assert data['data']['total_distance'] == 0
 | 
			
		||||
@@ -90,6 +91,7 @@ def test_single_user_with_activities(
 | 
			
		||||
    assert data['data']['location'] is None
 | 
			
		||||
    assert data['data']['timezone'] is None
 | 
			
		||||
    assert data['data']['weekm'] is False
 | 
			
		||||
    assert data['data']['language'] is None
 | 
			
		||||
    assert data['data']['nb_activities'] == 2
 | 
			
		||||
    assert data['data']['nb_sports'] == 2
 | 
			
		||||
    assert data['data']['total_distance'] == 22
 | 
			
		||||
@@ -175,18 +177,21 @@ def test_users_list(app, user_1, user_2, user_3):
 | 
			
		||||
    assert 'sam@test.com' in data['data']['users'][2]['email']
 | 
			
		||||
    assert data['data']['users'][0]['timezone'] is None
 | 
			
		||||
    assert data['data']['users'][0]['weekm'] is False
 | 
			
		||||
    assert data['data']['users'][0]['language'] is None
 | 
			
		||||
    assert data['data']['users'][0]['nb_activities'] == 0
 | 
			
		||||
    assert data['data']['users'][0]['nb_sports'] == 0
 | 
			
		||||
    assert data['data']['users'][0]['total_distance'] == 0
 | 
			
		||||
    assert data['data']['users'][0]['total_duration'] == '0:00:00'
 | 
			
		||||
    assert data['data']['users'][1]['timezone'] is None
 | 
			
		||||
    assert data['data']['users'][1]['weekm'] is False
 | 
			
		||||
    assert data['data']['users'][1]['language'] is None
 | 
			
		||||
    assert data['data']['users'][1]['nb_activities'] == 0
 | 
			
		||||
    assert data['data']['users'][1]['nb_sports'] == 0
 | 
			
		||||
    assert data['data']['users'][1]['total_distance'] == 0
 | 
			
		||||
    assert data['data']['users'][1]['total_duration'] == '0:00:00'
 | 
			
		||||
    assert data['data']['users'][2]['timezone'] is None
 | 
			
		||||
    assert data['data']['users'][2]['weekm'] is True
 | 
			
		||||
    assert data['data']['users'][2]['language'] is None
 | 
			
		||||
    assert data['data']['users'][2]['nb_activities'] == 0
 | 
			
		||||
    assert data['data']['users'][2]['nb_sports'] == 0
 | 
			
		||||
    assert data['data']['users'][2]['total_distance'] == 0
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ def test_user_model(app, user_1):
 | 
			
		||||
    assert serialized_user['picture'] is False
 | 
			
		||||
    assert serialized_user['timezone'] is None
 | 
			
		||||
    assert serialized_user['weekm'] is False
 | 
			
		||||
    assert serialized_user['language'] is None
 | 
			
		||||
    assert serialized_user['nb_activities'] == 0
 | 
			
		||||
    assert serialized_user['nb_sports'] == 0
 | 
			
		||||
    assert serialized_user['total_distance'] == 0
 | 
			
		||||
 
 | 
			
		||||
@@ -331,6 +331,7 @@ def get_user_status(user_id):
 | 
			
		||||
          "email": "sam@example.com",
 | 
			
		||||
          "first_name": null,
 | 
			
		||||
          "id": 2,
 | 
			
		||||
          "language": "en",
 | 
			
		||||
          "last_name": null,
 | 
			
		||||
          "location": null,
 | 
			
		||||
          "nb_activities": 6,
 | 
			
		||||
@@ -388,6 +389,7 @@ def edit_user(user_id):
 | 
			
		||||
          "email": "sam@example.com",
 | 
			
		||||
          "first_name": null,
 | 
			
		||||
          "id": 2,
 | 
			
		||||
          "language": "en",
 | 
			
		||||
          "last_name": null,
 | 
			
		||||
          "location": null,
 | 
			
		||||
          "nb_activities": 6,
 | 
			
		||||
@@ -412,6 +414,7 @@ def edit_user(user_id):
 | 
			
		||||
    :<json string password_conf: user password confirmation
 | 
			
		||||
    :<json string timezone: user time zone
 | 
			
		||||
    :<json string weekm: does week start on Monday?
 | 
			
		||||
    :<json string language: language preferences
 | 
			
		||||
 | 
			
		||||
    :reqheader Authorization: OAuth 2.0 Bearer Token
 | 
			
		||||
 | 
			
		||||
@@ -433,6 +436,7 @@ def edit_user(user_id):
 | 
			
		||||
        'last_name',
 | 
			
		||||
        'bio',
 | 
			
		||||
        'birth_date',
 | 
			
		||||
        'language',
 | 
			
		||||
        'location',
 | 
			
		||||
        'timezone',
 | 
			
		||||
        'weekm',
 | 
			
		||||
@@ -444,6 +448,7 @@ def edit_user(user_id):
 | 
			
		||||
    last_name = post_data.get('last_name')
 | 
			
		||||
    bio = post_data.get('bio')
 | 
			
		||||
    birth_date = post_data.get('birth_date')
 | 
			
		||||
    language = post_data.get('language')
 | 
			
		||||
    location = post_data.get('location')
 | 
			
		||||
    password = post_data.get('password')
 | 
			
		||||
    password_conf = post_data.get('password_conf')
 | 
			
		||||
@@ -465,6 +470,7 @@ def edit_user(user_id):
 | 
			
		||||
        user.first_name = first_name
 | 
			
		||||
        user.last_name = last_name
 | 
			
		||||
        user.bio = bio
 | 
			
		||||
        user.language = language
 | 
			
		||||
        user.location = location
 | 
			
		||||
        user.birth_date = (
 | 
			
		||||
            datetime.datetime.strptime(birth_date, '%Y-%m-%d')
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,7 @@ class User(db.Model):
 | 
			
		||||
    records = db.relationship(
 | 
			
		||||
        'Record', lazy=True, backref=db.backref('users', lazy='joined')
 | 
			
		||||
    )
 | 
			
		||||
    language = db.Column(db.String(50), nullable=True)
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return f'<User {self.username!r}>'
 | 
			
		||||
@@ -116,6 +117,7 @@ class User(db.Model):
 | 
			
		||||
            'picture': self.picture is not None,
 | 
			
		||||
            'timezone': self.timezone,
 | 
			
		||||
            'weekm': self.weekm,
 | 
			
		||||
            'language': self.language,
 | 
			
		||||
            'nb_activities': nb_activity,
 | 
			
		||||
            'nb_sports': len(sports),
 | 
			
		||||
            'total_distance': float(total[0]) if total[0] else 0,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
"""add weekm in 'User' table
 | 
			
		||||
"""add 'weekm' in 'User' table
 | 
			
		||||
 | 
			
		||||
Revision ID: 27425324c9e3
 | 
			
		||||
Revises: 096dd0b43beb
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										28
									
								
								fittrackee_api/migrations/versions/f69f1e413bde_.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								fittrackee_api/migrations/versions/f69f1e413bde_.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
"""add 'language' to 'User' table
 | 
			
		||||
 | 
			
		||||
Revision ID: f69f1e413bde
 | 
			
		||||
Revises: 27425324c9e3
 | 
			
		||||
Create Date: 2019-09-16 13:18:39.198777
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from alembic import op
 | 
			
		||||
import sqlalchemy as sa
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# revision identifiers, used by Alembic.
 | 
			
		||||
revision = 'f69f1e413bde'
 | 
			
		||||
down_revision = '27425324c9e3'
 | 
			
		||||
branch_labels = None
 | 
			
		||||
depends_on = None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def upgrade():
 | 
			
		||||
    # ### commands auto generated by Alembic - please adjust! ###
 | 
			
		||||
    op.add_column('users', sa.Column('language', sa.String(length=50), nullable=True))
 | 
			
		||||
    # ### end Alembic commands ###
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def downgrade():
 | 
			
		||||
    # ### commands auto generated by Alembic - please adjust! ###
 | 
			
		||||
    op.drop_column('users', 'language')
 | 
			
		||||
    # ### end Alembic commands ###
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
import i18next from 'i18next'
 | 
			
		||||
 | 
			
		||||
import FitTrackeeApi from '../fitTrackeeApi/index'
 | 
			
		||||
import { history } from '../index'
 | 
			
		||||
 | 
			
		||||
@@ -62,3 +64,7 @@ export const deleteData = (target, id) => dispatch => {
 | 
			
		||||
    })
 | 
			
		||||
    .catch(error => dispatch(setError(`${target}: ${error}`)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const updateLanguage = language => dispatch => {
 | 
			
		||||
  i18next.changeLanguage(language).then(dispatch(setLanguage(language)))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ import FitTrackeeGenericApi from '../fitTrackeeApi'
 | 
			
		||||
import FitTrackeeApi from '../fitTrackeeApi/user'
 | 
			
		||||
import { history } from '../index'
 | 
			
		||||
import { generateIds } from '../utils'
 | 
			
		||||
import { getOrUpdateData } from './index'
 | 
			
		||||
import { getOrUpdateData, updateLanguage } from './index'
 | 
			
		||||
 | 
			
		||||
const AuthError = message => ({ type: 'AUTH_ERROR', message })
 | 
			
		||||
 | 
			
		||||
@@ -34,6 +34,9 @@ export const getProfile = () => dispatch =>
 | 
			
		||||
      if (ret.status === 'success') {
 | 
			
		||||
        dispatch(getOrUpdateData('getData', 'sports'))
 | 
			
		||||
        ret.data.isAuthenticated = true
 | 
			
		||||
        if (ret.data.language) {
 | 
			
		||||
          dispatch(updateLanguage(ret.data.language))
 | 
			
		||||
        }
 | 
			
		||||
        return dispatch(ProfileSuccess(ret.data))
 | 
			
		||||
      }
 | 
			
		||||
      return dispatch(ProfileError(ret.message))
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,11 @@
 | 
			
		||||
import React, { Component } from 'react'
 | 
			
		||||
import i18next from 'i18next'
 | 
			
		||||
import { connect } from 'react-redux'
 | 
			
		||||
 | 
			
		||||
import { ReactComponent as EnFlag } from '../../images/flags/en.svg'
 | 
			
		||||
import { ReactComponent as FrFlag } from '../../images/flags/fr.svg'
 | 
			
		||||
import { setLanguage } from '../../actions/index'
 | 
			
		||||
import { updateLanguage } from '../../actions/index'
 | 
			
		||||
 | 
			
		||||
const languages = [
 | 
			
		||||
export const languages = [
 | 
			
		||||
  {
 | 
			
		||||
    name: 'en',
 | 
			
		||||
    selected: true,
 | 
			
		||||
@@ -69,8 +68,7 @@ export default connect(
 | 
			
		||||
  dispatch => ({
 | 
			
		||||
    onUpdateLanguage: (lang, selected) => {
 | 
			
		||||
      if (lang !== selected) {
 | 
			
		||||
        i18next.changeLanguage(lang)
 | 
			
		||||
        dispatch(setLanguage(lang))
 | 
			
		||||
        dispatch(updateLanguage(lang))
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
 
 | 
			
		||||
@@ -60,6 +60,9 @@ function Profile({ message, onDeletePicture, onUploadPicture, t, user }) {
 | 
			
		||||
                    <p>
 | 
			
		||||
                      {t('user:Bio')}: {user.bio}
 | 
			
		||||
                    </p>
 | 
			
		||||
                    <p>
 | 
			
		||||
                      {t('user:Language')}: {user.language}
 | 
			
		||||
                    </p>
 | 
			
		||||
                    <p>
 | 
			
		||||
                      {t('user:Timezone')}: {user.timezone}
 | 
			
		||||
                    </p>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ import TimezonePicker from 'react-timezone'
 | 
			
		||||
 | 
			
		||||
import { handleProfileFormSubmit } from '../../actions/user'
 | 
			
		||||
import { history } from '../../index'
 | 
			
		||||
import { languages } from '../NavBar/LanguageDropdown'
 | 
			
		||||
 | 
			
		||||
class ProfileEdit extends React.Component {
 | 
			
		||||
  constructor(props, context) {
 | 
			
		||||
@@ -182,6 +183,23 @@ class ProfileEdit extends React.Component {
 | 
			
		||||
                              />
 | 
			
		||||
                            </label>
 | 
			
		||||
                          </div>
 | 
			
		||||
                          <div className="form-group">
 | 
			
		||||
                            <label>
 | 
			
		||||
                              {t('user:Language')}:
 | 
			
		||||
                              <select
 | 
			
		||||
                                name="language"
 | 
			
		||||
                                className="form-control input-lg"
 | 
			
		||||
                                value={formData.language}
 | 
			
		||||
                                onChange={e => this.handleFormChange(e)}
 | 
			
		||||
                              >
 | 
			
		||||
                                {languages.map(lang => (
 | 
			
		||||
                                  <option value={lang.name} key={lang.name}>
 | 
			
		||||
                                    {lang.name}
 | 
			
		||||
                                  </option>
 | 
			
		||||
                                ))}
 | 
			
		||||
                              </select>
 | 
			
		||||
                            </label>
 | 
			
		||||
                          </div>
 | 
			
		||||
                          <div className="form-group">
 | 
			
		||||
                            <label>
 | 
			
		||||
                              {t('user:Timezone')}:
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@
 | 
			
		||||
  "Enter the password confirmation": "Enter the password confirmation",
 | 
			
		||||
  "First day of week": "First day of week",
 | 
			
		||||
  "First Name": "First Name",
 | 
			
		||||
  "Language": "Language",
 | 
			
		||||
  "Last Name": "Last Name",
 | 
			
		||||
  "Location": "Location",
 | 
			
		||||
  "loggedOut": "You are now logged out. Click <1>here</1> to log back in.",
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@
 | 
			
		||||
  "Enter the password confirmation": "Confirmer le mot de passe",
 | 
			
		||||
  "First day of week": "Premier jour de la semaine",
 | 
			
		||||
  "First Name": "Prénom",
 | 
			
		||||
  "Language": "Langue",
 | 
			
		||||
  "Last Name": "Nom",
 | 
			
		||||
  "Location": "Lieu",
 | 
			
		||||
  "loggedOut": "Vous êtes déconnecté. Cliquez <1>ici</1> pour vous reconnecter.",
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user