...
 
Commits (3)
...@@ -6,6 +6,7 @@ from __future__ import absolute_import, print_function, unicode_literals ...@@ -6,6 +6,7 @@ from __future__ import absolute_import, print_function, unicode_literals
import collections import collections
import logging import logging
import os
import flatisfy.filters import flatisfy.filters
from flatisfy import database from flatisfy import database
...@@ -203,6 +204,14 @@ def import_and_filter(config, load_from_db=False): ...@@ -203,6 +204,14 @@ def import_and_filter(config, load_from_db=False):
if config["send_email"]: if config["send_email"]:
email.send_notification(config, new_flats) email.send_notification(config, new_flats)
# Touch a file to indicate last update timestamp
ts_file = os.path.join(
config["data_directory"],
"timestamp"
)
with open(ts_file, 'w'):
os.utime(ts_file, None)
LOGGER.info("Done!") LOGGER.info("Done!")
......
...@@ -107,6 +107,8 @@ def get_app(config): ...@@ -107,6 +107,8 @@ def get_app(config):
app.route("/api/v1/opendata/postal_codes", "GET", app.route("/api/v1/opendata/postal_codes", "GET",
api_routes.opendata_postal_codes_v1) api_routes.opendata_postal_codes_v1)
app.route("/api/v1/metadata", ["GET", "OPTIONS"], api_routes.metadata_v1)
# Index # Index
app.route("/", "GET", lambda: _serve_static_file("index.html")) app.route("/", "GET", lambda: _serve_static_file("index.html"))
......
...@@ -161,3 +161,10 @@ export const doSearch = function (query, callback) { ...@@ -161,3 +161,10 @@ export const doSearch = function (query, callback) {
console.error('Unable to perform search: ' + ex) console.error('Unable to perform search: ' + ex)
}) })
} }
export const getMetadata = function (callback) {
fetch('/api/v1/metadata', { credentials: 'same-origin' })
.then(response => response.json())
.then(json => callback(json.data))
.catch(ex => console.error('Unable to fetch application metadata: ' + ex))
}
...@@ -56,13 +56,13 @@ ...@@ -56,13 +56,13 @@
<script> <script>
import 'font-awesome-webpack' import 'font-awesome-webpack'
import FlatsTableLine from './flatstableline.vue'; import FlatsTableLine from './flatstableline.vue'
import { capitalize } from '../tools' import { capitalize } from '../tools'
export default { export default {
components: { components: {
FlatsTableLine, FlatsTableLine
}, },
data () { data () {
...@@ -129,7 +129,7 @@ export default { ...@@ -129,7 +129,7 @@ export default {
this.sortBy = field this.sortBy = field
} }
}, },
capitalize: capitalize, capitalize: capitalize
} }
} }
</script> </script>
......
...@@ -66,33 +66,33 @@ export default { ...@@ -66,33 +66,33 @@ export default {
props: { props: {
flat: Object, flat: Object,
showNotationColumn: Boolean, showNotationColumn: Boolean,
showNotes: Boolean, showNotes: Boolean
}, },
computed: { computed: {
capitalizedStatus() { capitalizedStatus () {
return capitalize(this.$t('status.followed')); return capitalize(this.$t('status.followed'))
}, },
photo() { photo () {
if (this.flat.photos && this.flat.photos.length > 0) { if (this.flat.photos && this.flat.photos.length > 0) {
if (this.flat.photos[0].local) { if (this.flat.photos[0].local) {
return `/data/img/${this.flat.photos[0].local}`; return `/data/img/${this.flat.photos[0].local}`
} }
return this.flat.photos[0].url; return this.flat.photos[0].url
} }
return null; return null
},
notationRange() {
return range(this.flat.notation);
}, },
notationRange () {
return range(this.flat.notation)
}
}, },
methods: { methods: {
updateFlatStatus (id, status) { updateFlatStatus (id, status) {
this.$store.dispatch('updateFlatStatus', { flatId: id, newStatus: status }) this.$store.dispatch('updateFlatStatus', { flatId: id, newStatus: status })
}, }
}, }
}; }
</script> </script>
<style scoped> <style scoped>
......
...@@ -28,14 +28,14 @@ export default { ...@@ -28,14 +28,14 @@ export default {
}, },
computed: { computed: {
photosURLOrLocal() { photosURLOrLocal () {
return this.photos.map(photo => { return this.photos.map(photo => {
if (photo.local) { if (photo.local) {
return `/data/img/${photo.local}`; return `/data/img/${photo.local}`
} }
return photo.url; return photo.url
}); })
}, }
}, },
created () { created () {
......
...@@ -19,6 +19,7 @@ export default { ...@@ -19,6 +19,7 @@ export default {
}, },
home: { home: {
'new_available_flats': 'New available flats', 'new_available_flats': 'New available flats',
'Last_update': 'Last update:',
'show_expired_flats': 'Show expired flats' 'show_expired_flats': 'Show expired flats'
}, },
flatListing: { flatListing: {
...@@ -41,7 +42,7 @@ export default { ...@@ -41,7 +42,7 @@ export default {
'utilities_included': '(utilities included)', 'utilities_included': '(utilities included)',
'utilities_excluded': '(utilities excluded)', 'utilities_excluded': '(utilities excluded)',
'Description': 'Description', 'Description': 'Description',
'First_posted' : 'First posted', 'First_posted': 'First posted',
'Details': 'Details', 'Details': 'Details',
'Metadata': 'Metadata', 'Metadata': 'Metadata',
'postal_code': 'Postal code', 'postal_code': 'Postal code',
......
...@@ -19,6 +19,7 @@ export default { ...@@ -19,6 +19,7 @@ export default {
}, },
home: { home: {
'new_available_flats': 'Nouveaux appartements disponibles', 'new_available_flats': 'Nouveaux appartements disponibles',
'Last_update': 'Dernière mise à jour :',
'show_expired_flats': 'Montrer les annonces expirées' 'show_expired_flats': 'Montrer les annonces expirées'
}, },
flatListing: { flatListing: {
......
...@@ -50,5 +50,11 @@ export default { ...@@ -50,5 +50,11 @@ export default {
api.doSearch(query, flats => { api.doSearch(query, flats => {
commit(types.REPLACE_FLATS, { flats }) commit(types.REPLACE_FLATS, { flats })
}) })
},
getMetadata ({ commit }) {
commit(types.IS_LOADING)
api.getMetadata(metadata => {
commit(types.RECEIVE_METADATA, { metadata })
})
} }
} }
...@@ -67,5 +67,7 @@ export default { ...@@ -67,5 +67,7 @@ export default {
timeToPlaces: (state, getters) => (constraintName) => { timeToPlaces: (state, getters) => (constraintName) => {
return state.timeToPlaces[constraintName] return state.timeToPlaces[constraintName]
} },
metadata: state => state.metadata
} }
...@@ -5,4 +5,5 @@ export const UPDATE_FLAT_NOTES = 'UPDATE_FLAT_NOTES' ...@@ -5,4 +5,5 @@ export const UPDATE_FLAT_NOTES = 'UPDATE_FLAT_NOTES'
export const UPDATE_FLAT_NOTATION = 'UPDATE_FLAT_NOTATION' export const UPDATE_FLAT_NOTATION = 'UPDATE_FLAT_NOTATION'
export const UPDATE_FLAT_VISIT_DATE = 'UPDATE_FLAT_VISIT_DATE' export const UPDATE_FLAT_VISIT_DATE = 'UPDATE_FLAT_VISIT_DATE'
export const RECEIVE_TIME_TO_PLACES = 'RECEIVE_TIME_TO_PLACES' export const RECEIVE_TIME_TO_PLACES = 'RECEIVE_TIME_TO_PLACES'
export const RECEIVE_METADATA = 'RECEIVE_METADATA'
export const IS_LOADING = 'IS_LOADING' export const IS_LOADING = 'IS_LOADING'
...@@ -5,6 +5,7 @@ import * as types from './mutations-types' ...@@ -5,6 +5,7 @@ import * as types from './mutations-types'
export const state = { export const state = {
flats: [], flats: [],
timeToPlaces: [], timeToPlaces: [],
metadata: [],
loading: 0 loading: 0
} }
...@@ -58,6 +59,10 @@ export const mutations = { ...@@ -58,6 +59,10 @@ export const mutations = {
state.timeToPlaces = timeToPlaces state.timeToPlaces = timeToPlaces
state.loading -= 1 state.loading -= 1
}, },
[types.RECEIVE_METADATA] (state, { metadata }) {
state.metadata = metadata
state.loading -= 1
},
[types.IS_LOADING] (state) { [types.IS_LOADING] (state) {
state.loading += 1 state.loading += 1
} }
......
...@@ -4,6 +4,11 @@ ...@@ -4,6 +4,11 @@
<h2> <h2>
{{ $t("home.new_available_flats") }} {{ $t("home.new_available_flats") }}
<template v-if="lastUpdate">
<label class="show-last-update">
{{ $t("home.Last_update") }} {{ lastUpdate.fromNow() }}
</label>
</template>
<label class="show-expired-flats-label"> <label class="show-expired-flats-label">
<input type="checkbox" class="show-expired-flats-checkbox" v-model="showExpiredFlats" /> <input type="checkbox" class="show-expired-flats-checkbox" v-model="showExpiredFlats" />
{{ $t("home.show_expired_flats") }} {{ $t("home.show_expired_flats") }}
...@@ -33,6 +38,7 @@ ...@@ -33,6 +38,7 @@
<script> <script>
import FlatsMap from '../components/flatsmap.vue' import FlatsMap from '../components/flatsmap.vue'
import FlatsTable from '../components/flatstable.vue' import FlatsTable from '../components/flatstable.vue'
import moment from 'moment'
export default { export default {
components: { components: {
...@@ -47,12 +53,14 @@ export default { ...@@ -47,12 +53,14 @@ export default {
this.$store.dispatch('getAllFlats') this.$store.dispatch('getAllFlats')
// Fetch time to places when the component is created // Fetch time to places when the component is created
this.$store.dispatch('getAllTimeToPlaces') this.$store.dispatch('getAllTimeToPlaces')
// Fetch application metadata when the component is created
this.$store.dispatch('getMetadata')
}, },
data () { data () {
return { return {
showExpiredFlats: false, showExpiredFlats: false
}; }
}, },
computed: { computed: {
...@@ -71,6 +79,14 @@ export default { ...@@ -71,6 +79,14 @@ export default {
timeToPlaces () { timeToPlaces () {
return this.$store.getters.allTimeToPlaces return this.$store.getters.allTimeToPlaces
}, },
lastUpdate () {
var metadata = this.$store.getters.metadata
var lastUpdateDate = moment.unix(metadata['last_update'])
if (!lastUpdateDate.isValid()) {
lastUpdateDate = 0
}
return lastUpdateDate
},
isLoading () { isLoading () {
return this.$store.getters.isLoading return this.$store.getters.isLoading
} }
...@@ -88,4 +104,9 @@ h2 { ...@@ -88,4 +104,9 @@ h2 {
font-weight: initial; font-weight: initial;
font-size: initial; font-size: initial;
} }
.show-last-update {
font-weight: initial;
font-size: initial;
}
</style> </style>
...@@ -10,6 +10,7 @@ import datetime ...@@ -10,6 +10,7 @@ import datetime
import itertools import itertools
import json import json
import re import re
import os
import bottle import bottle
import vobject import vobject
...@@ -146,7 +147,8 @@ def index_v1(): ...@@ -146,7 +147,8 @@ def index_v1():
"flat": "/api/v1/flat/:id", "flat": "/api/v1/flat/:id",
"search": "/api/v1/search", "search": "/api/v1/search",
"ics": "/api/v1/ics/visits.ics", "ics": "/api/v1/ics/visits.ics",
"time_to_places": "/api/v1/time_to_places" "time_to_places": "/api/v1/time_to_places",
"metadata": "/api/v1/metadata"
} }
...@@ -499,3 +501,31 @@ def opendata_postal_codes_v1(db): ...@@ -499,3 +501,31 @@ def opendata_postal_codes_v1(db):
} }
except Exception as exc: # pylint: disable= broad-except except Exception as exc: # pylint: disable= broad-except
return JSONError(500, str(exc)) return JSONError(500, str(exc))
def metadata_v1(config):
"""
API v1 metadata of the application.
Example::
GET /api/v1/metadata
:return: The application metadata.
"""
if bottle.request.method == 'OPTIONS':
# CORS
return {}
try:
ts_file = os.path.join(
config['data_directory'],
'timestamp'
)
ts = os.path.getmtime(ts_file)
return {
'data': {
'last_update': ts
}
}
except Exception as exc: # pylint: disable= broad-except
return JSONError(500, str(exc))