Merge branch 'last-update' into 'master'
Last update See merge request phyks/Flatisfy!37
This commit is contained in:
commit
5083f002d2
@ -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):
|
|||||||
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):
|
|||||||
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) {
|
|||||||
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 @@
|
|||||||
<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 {
|
|||||||
this.sortBy = field
|
this.sortBy = field
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
capitalize: capitalize,
|
capitalize: capitalize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -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 {
|
|||||||
},
|
},
|
||||||
|
|
||||||
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 {
|
|||||||
},
|
},
|
||||||
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 {
|
|||||||
'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 {
|
|||||||
},
|
},
|
||||||
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 {
|
|||||||
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 {
|
|||||||
|
|
||||||
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'
|
|||||||
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'
|
|||||||
export const state = {
|
export const state = {
|
||||||
flats: [],
|
flats: [],
|
||||||
timeToPlaces: [],
|
timeToPlaces: [],
|
||||||
|
metadata: [],
|
||||||
loading: 0
|
loading: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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 @@
|
|||||||
|
|
||||||
<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 @@
|
|||||||
<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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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
|
|||||||
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():
|
|||||||
"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):
|
|||||||
}
|
}
|
||||||
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))
|
||||||
|
Loading…
Reference in New Issue
Block a user