Use stars to note flats

Instead of a binary "followed" / "not followed" status, use 5 stars to
allow users to give a note to a flat between 0 (not followed) and 5. Any
note different from zero add a "followed" status.

Closes issue #36.
This commit is contained in:
Lucas Verney 2017-05-03 22:10:24 +02:00
parent 69588a9601
commit 0e3d1576b2
No known key found for this signature in database
GPG Key ID: 75B45CF41F334690
10 changed files with 119 additions and 15 deletions

View File

@ -10,7 +10,9 @@ import logging
import arrow import arrow
import enum import enum
from sqlalchemy import Column, DateTime, Enum, Float, String, Text from sqlalchemy import (
Column, DateTime, Enum, Float, SmallInteger, String, Text
)
from flatisfy.database.base import BASE from flatisfy.database.base import BASE
from flatisfy.database.types import MagicJSON from flatisfy.database.types import MagicJSON
@ -77,6 +79,7 @@ class Flat(BASE):
urls = Column(MagicJSON) urls = Column(MagicJSON)
merged_ids = Column(MagicJSON) merged_ids = Column(MagicJSON)
notes = Column(Text) notes = Column(Text)
notation = Column(SmallInteger, default=0)
# Flatisfy data # Flatisfy data
# TODO: Should be in another table with relationships # TODO: Should be in another table with relationships

View File

@ -80,6 +80,8 @@ def get_app(config):
api_routes.update_flat_status_v1) api_routes.update_flat_status_v1)
app.route("/api/v1/flat/:flat_id/notes", "POST", app.route("/api/v1/flat/:flat_id/notes", "POST",
api_routes.update_flat_notes_v1) api_routes.update_flat_notes_v1)
app.route("/api/v1/flat/:flat_id/notation", "POST",
api_routes.update_flat_notation_v1)
app.route("/api/v1/search", "POST", api_routes.search_v1) app.route("/api/v1/search", "POST", api_routes.search_v1)

View File

@ -88,6 +88,24 @@ export const updateFlatNotes = function (flatId, newNotes, callback) {
}) })
} }
export const updateFlatNotation = function (flatId, newNotation, callback) {
fetch(
'/api/v1/flat/' + encodeURIComponent(flatId) + '/notation',
{
credentials: 'same-origin',
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
notation: newNotation
})
}
).then(callback).catch(function (ex) {
console.error('Unable to update flat notation: ' + ex)
})
}
export const getTimeToPlaces = function (callback) { export const getTimeToPlaces = function (callback) {
fetch('/api/v1/time_to/places', { credentials: 'same-origin' }) fetch('/api/v1/time_to/places', { credentials: 'same-origin' })
.then(function (response) { .then(function (response) {

View File

@ -36,7 +36,7 @@
<tbody> <tbody>
<tr v-for="flat in sortedFlats" :key="flat.id"> <tr v-for="flat in sortedFlats" :key="flat.id">
<td> <td>
<template v-if="flat.status === 'followed'"> <template v-for="n in range(flat.notation)">
<i class="fa fa-star" aria-hidden="true" :title="capitalize($t('status.followed'))"></i> <i class="fa fa-star" aria-hidden="true" :title="capitalize($t('status.followed'))"></i>
</template> </template>
@ -82,7 +82,7 @@
<script> <script>
import 'font-awesome-webpack' import 'font-awesome-webpack'
import { capitalize } from '../tools' import { capitalize, range } from '../tools'
export default { export default {
data () { data () {
@ -123,7 +123,8 @@ export default {
this.sortBy = field this.sortBy = field
} }
}, },
capitalize: capitalize capitalize: capitalize,
range: range
} }
} }
</script> </script>

View File

@ -27,6 +27,12 @@ export default {
commit(types.UPDATE_FLAT_STATUS, { flatId, newStatus }) commit(types.UPDATE_FLAT_STATUS, { flatId, newStatus })
}) })
}, },
updateFlatNotation ({ commit }, { flatId, newNotation }) {
commit(types.IS_LOADING)
api.updateFlatNotation(flatId, newNotation, response => {
commit(types.UPDATE_FLAT_NOTATION, { flatId, newNotation })
})
},
updateFlatNotes ({ commit }, { flatId, newNotes }) { updateFlatNotes ({ commit }, { flatId, newNotes }) {
commit(types.IS_LOADING) commit(types.IS_LOADING)
api.updateFlatNotes(flatId, newNotes, response => { api.updateFlatNotes(flatId, newNotes, response => {

View File

@ -2,5 +2,6 @@ export const REPLACE_FLATS = 'REPLACE_FLATS'
export const MERGE_FLATS = 'MERGE_FLATS' export const MERGE_FLATS = 'MERGE_FLATS'
export const UPDATE_FLAT_STATUS = 'UPDATE_FLAT_STATUS' export const UPDATE_FLAT_STATUS = 'UPDATE_FLAT_STATUS'
export const UPDATE_FLAT_NOTES = 'UPDATE_FLAT_NOTES' export const UPDATE_FLAT_NOTES = 'UPDATE_FLAT_NOTES'
export const UPDATE_FLAT_NOTATION = 'UPDATE_FLAT_NOTATION'
export const RECEIVE_TIME_TO_PLACES = 'RECEIVE_TIME_TO_PLACES' export const RECEIVE_TIME_TO_PLACES = 'RECEIVE_TIME_TO_PLACES'
export const IS_LOADING = 'IS_LOADING' export const IS_LOADING = 'IS_LOADING'

View File

@ -39,6 +39,13 @@ export const mutations = {
Vue.set(state.flats[index], 'notes', newNotes) Vue.set(state.flats[index], 'notes', newNotes)
} }
}, },
[types.UPDATE_FLAT_NOTATION] (state, { flatId, newNotation }) {
state.loading = false
const index = state.flats.findIndex(flat => flat.id === flatId)
if (index > -1) {
Vue.set(state.flats[index], 'notation', newNotation)
}
},
[types.RECEIVE_TIME_TO_PLACES] (state, { timeToPlaces }) { [types.RECEIVE_TIME_TO_PLACES] (state, { timeToPlaces }) {
state.loading = false state.loading = false
state.timeToPlaces = timeToPlaces state.timeToPlaces = timeToPlaces

View File

@ -19,3 +19,7 @@ export function findFlatGPS (flat) {
export function capitalize (string) { export function capitalize (string) {
return string.charAt(0).toUpperCase() + string.slice(1) return string.charAt(0).toUpperCase() + string.slice(1)
} }
export function range (n) {
return [...Array(n).keys()]
}

View File

@ -136,17 +136,15 @@
<nav> <nav>
<ul> <ul>
<template v-if="flat.status !== 'user_deleted'"> <template v-if="flat.status !== 'user_deleted'">
<li> <li ref="notationButton">
<template v-if="flat.status !== 'followed'"> <template v-for="n in range(notation)">
<button v-on:click="updateFlatStatus('followed')"> <button class="btnIcon" v-on:mouseover="handleNotationHover(n)" v-on:mouseout="handleNotationOut()" v-on:click="updateFlatNotation(n)">
<i class="fa fa-star" aria-hidden="true"></i> <i class="fa fa-star" aria-hidden="true"></i>
{{ $t("common.Follow") }}
</button> </button>
</template> </template>
<template v-else> <template v-for="n in range(5 - notation)">
<button v-on:click="updateFlatStatus('new')"> <button class="btnIcon" v-on:mouseover="handleNotationHover(notation + n)" v-on:mouseout="handleNotationOut()" v-on:click="updateFlatNotation(notation + n)">
<i class="fa fa-star-o" aria-hidden="true"></i> <i class="fa fa-star-o" aria-hidden="true"></i>
{{ $t("common.Unfollow") }}
</button> </button>
</template> </template>
</li> </li>
@ -181,7 +179,7 @@ import 'font-awesome-webpack'
import FlatsMap from '../components/flatsmap.vue' import FlatsMap from '../components/flatsmap.vue'
import Slider from '../components/slider.vue' import Slider from '../components/slider.vue'
import { capitalize } from '../tools' import { capitalize, range } from '../tools'
export default { export default {
components: { components: {
@ -202,6 +200,12 @@ export default {
'$route': 'fetchData' '$route': 'fetchData'
}, },
data () {
return {
'overloadNotation': null
}
},
computed: { computed: {
flatMarkers () { flatMarkers () {
return this.$store.getters.flatsMarkers(this.$router, flat => flat.id === this.$route.params.id) return this.$store.getters.flatsMarkers(this.$router, flat => flat.id === this.$route.params.id)
@ -212,6 +216,12 @@ export default {
flat () { flat () {
return this.$store.getters.flat(this.$route.params.id) return this.$store.getters.flat(this.$route.params.id)
}, },
notation () {
if (this.overloadNotation) {
return this.overloadNotation
}
return this.flat.notation
},
journeys () { journeys () {
if (Object.keys(this.flat.flatisfy_time_to).length > 0) { if (Object.keys(this.flat.flatisfy_time_to).length > 0) {
const journeys = [] const journeys = []
@ -246,8 +256,16 @@ export default {
this.$store.dispatch('getAllTimeToPlaces') this.$store.dispatch('getAllTimeToPlaces')
}, },
updateFlatStatus (status) { updateFlatNotation (notation) {
this.$store.dispatch('updateFlatStatus', { flatId: this.$route.params.id, newStatus: status }) notation = notation + 1
if (notation === this.flat.notation) {
this.$store.dispatch('updateFlatNotation', { flatId: this.$route.params.id, newNotation: 0 })
this.$store.dispatch('updateFlatStatus', { flatId: this.$route.params.id, newStatus: 'new' })
} else {
this.$store.dispatch('updateFlatNotation', { flatId: this.$route.params.id, newNotation: notation })
this.$store.dispatch('updateFlatStatus', { flatId: this.$route.params.id, newStatus: 'followed' })
}
}, },
updateFlatNotes () { updateFlatNotes () {
@ -263,7 +281,17 @@ export default {
return minutes + ' ' + this.$tc('common.mins', minutes) return minutes + ' ' + this.$tc('common.mins', minutes)
}, },
capitalize: capitalize handleNotationHover (n) {
this.overloadNotation = n + 1
},
handleNotationOut () {
this.overloadNotation = null
},
capitalize: capitalize,
range: range
} }
} }
</script> </script>
@ -328,4 +356,10 @@ td {
list-style-position: outside; list-style-position: outside;
list-style-type: none; list-style-type: none;
} }
.btnIcon {
border: none;
width: auto;
background-color: transparent;
}
</style> </style>

View File

@ -174,6 +174,34 @@ def update_flat_notes_v1(flat_id, db):
} }
def update_flat_notation_v1(flat_id, db):
"""
API v1 route to update flat notation:
POST /api/v1/flat/:flat_id/notation
Data: {
"notation": "NEW_NOTATION"
}
:return: The new flat object in a JSON ``data`` dict.
"""
flat = db.query(flat_model.Flat).filter_by(id=flat_id).first()
if not flat:
return bottle.HTTPError(404, "No flat with id {}.".format(flat_id))
try:
flat.notation = json.load(bottle.request.body)["notation"]
assert flat.notes >= 0 and flat.notes <= 5
except (AssertionError, ValueError, KeyError):
return bottle.HTTPError(400, "Invalid notation provided.")
json_flat = flat.json_api_repr()
return {
"data": json_flat
}
def time_to_places_v1(config): def time_to_places_v1(config):
""" """
API v1 route to fetch the details of the places to compute time to. API v1 route to fetch the details of the places to compute time to.