Commit 535d36fd authored by hypsug0's avatar hypsug0
Browse files

wip: autocomplete vue-select for users

parent 9c3ea1b6
# import the logging library
import logging
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Q
from django.db.models.query import QuerySet
from rest_framework import generics
from sights.models import Session
from .models import Profile
from .serializer import ProfileSearchSerializer
# Get an instance of a logger
logger = logging.getLogger(__name__)
def filterUser(qs: QuerySet, q: str) -> QuerySet:
return qs.filter(
Q(username__icontains=q)
| Q(first_name__icontains=q)
| Q(last_name__icontains=q)
| Q(email__icontains=q)
)
class ProfileAutocompleteSearch(
LoginRequiredMixin,
generics.ListAPIView,
):
pagination_class = None
serializer_class = ProfileSearchSerializer
def get_queryset(self):
qs = Profile.objects
q = self.request.query_params.get("q", None)
id = self.request.query_params.get("id", None)
session = self.request.query_params.get("session", None)
logger.debug(f"ProfileAutocompleteSearch API querystring {q} {id} {session}")
if id:
qs = qs.filter(id=int(id))
result = qs.all()
if q and not id:
if session:
mainobs = filterUser(Session.objects.get(pk=session).main_observer.all(), q)
otherobs = filterUser(Session.objects.get(pk=session).other_observer.all(), q)
result = set(mainobs + otherobs)
else:
qs = filterUser(qs, q)
result = qs.order_by("get_full_name").order_by("is_active").all()
logger.debug(f"ProfileAutocompleteSearch API result {result}")
return result
from rest_framework import serializers
from .models import Profile
class ProfileSearchSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ("id", "get_full_name")
from django.urls import path
from .api import ProfileAutocompleteSearch
from .views import (
MyProfileDetail,
MyProfileDetailExport,
......@@ -29,6 +30,7 @@ urlpatterns = [
MyProfileDetailExport.as_view(),
name="myprofile_detail_export",
),
path("api/v1/profile/search", ProfileAutocompleteSearch.as_view(), name="profile_search_api"),
path("myprofile/update", MyProfileUpdate.as_view(), name="myprofile_update"),
path("list", UserListView.as_view(), name="user_search"),
]
......@@ -227,10 +227,7 @@ const placeSearchBlock = () => {
// moreInfoElement("Types d'observations", contact?.contacts),
moreInfoElement('Commentaire', comment),
moreInfoElement('Site géré', is_managed ? 'Oui' : 'Non'),
moreInfoElement(
'Convention refuge',
convention ? 'Oui' : 'Non'
),
moreInfoElement('Convention refuge', convention ? 'Oui' : 'Non'),
]
div.append(...dataToDisplay.filter((p) => p !== null))
return div
......@@ -256,7 +253,10 @@ const placeSearchBlock = () => {
},
width: '100',
},
{ data: (row) => `<a href="/place/${row.id}/detail" target="_blank">${row.properties.name}</a>` },
{
data: (row) =>
`<a href="/place/${row.id}/detail" target="_blank">${row.properties.name}</a>`,
},
{ data: 'properties.altitude' },
{
data: (row) => {
......@@ -1308,6 +1308,96 @@ Vue.component('dictSelect', {
},
})
Vue.component('userSelectOne', {
data() {
return {
options: [],
}
},
props: {
value: {
type: Number,
},
session: {
type: Number,
},
},
template: `<v-select class="form-control v-select-style" label="get_full_name" :reduce="e => e.id" :filterable="false" :options="options" @search="onSearch" v-model="model">
<template slot="no-options">
Tapez pour rechercher un utilisateur
</template>
<template v-slot:option="option">
{{ option.get_full_name }}
</template>
<template v-slot:selected-option="option">
{{ option.get_full_name }}
</template>
</v-select>`,
computed: {
model: {
get() {
return this.value
},
set(newValue) {
this.$emit('input', newValue)
},
},
},
methods: {
onSearch(search, loading) {
if (search.length > 2) {
loading(true)
this.search(loading, search, this)
}
},
onInit() {
if (this.model) {
this.apiCall(null, this.model)
}
},
apiCall(q= null, id= null) {
const qsParams = [];
let qs;
if (id) {
qs = `id=${id}`
}
else {
qsParams.push(`q=${escape(q)}`)
if (session) {
qs.push(`session=${this.session}`)
}
qs = qsParams.join('&')
}
console.trace('User Search QS', qs)
$http
.get(`/accounts/api/v1/profile/search?${qs}`)
.then((response) => {
this.options = response.data
loading(false)
})
.catch(function (error) {
console.error(error)
})
},
search: _debounce((loading, search, vm) => {
$http
.get(`/accounts/api/v1/profile/search?q=${escape(search)}`)
.then((response) => {
vm.options = response.data
console.debug('iotuibs', this.options)
loading(false)
})
.catch(function (error) {
console.error(error)
})
}, 350),
},
mounted() {
this.onInit()
}
})
const sigthingFormApp = (id_session, type_contact) => {
VeeValidate.extend('positive', (value) => {
return value >= 0
......@@ -1581,7 +1671,9 @@ const sigthingFormApp = (id_session, type_contact) => {
getDictSpecie() {},
getDicts() {
this.dicts = JSON.parse(localStorage.getItem('dicts'))
this.dictSpecies = JSON.parse(localStorage.getItem('dictSpecies'))
this.dictSpecies = JSON.parse(
localStorage.getItem('dictSpecies')
)
// $http
// .get('/api/v1/dicts')
......@@ -1643,7 +1735,9 @@ const sigthingFormApp = (id_session, type_contact) => {
sumCountDetails(is) {
const CountSum = (a, c) => a + parseInt(c.count)
// const notNullData = this.data[is].countdetails.filter(e => e.count !== null)
return this.data[is].countdetails.filter(e => e.count !== null).reduce(CountSum, 0)
return this.data[is].countdetails
.filter((e) => e.count !== null)
.reduce(CountSum, 0)
},
getData() {
if (Object.entries(this.dicts).length === 0) {
......
......@@ -38,6 +38,16 @@ if (csrftoken) {
$http.defaults.headers.common['X-CSRFToken'] = csrftoken
}
const _debounce = (func, timeout = 300) => {
let timer
return (...args) => {
clearTimeout(timer)
timer = setTimeout(() => {
func.apply(this, args)
}, timeout)
}
}
const storeDicts = () => {
// if (localStorage.getItem('dicts') === null) {
if (true) {
......
from django.contrib.postgres.aggregates import StringAgg
from django.db.models import Max
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.serializers import SerializerMethodField
from rest_framework_gis import serializers
from accounts.models import Profile
from dicts.models import MetaPlaceType, TypePlace
from geodata.models import Municipality
from sights.models import MetaPlace, Place, Session
from sights.models import MetaPlace, Place
class CountModelMixin(object):
......@@ -68,9 +65,6 @@ class PlaceSerializer(serializers.GeoFeatureModelSerializer):
type_data = PlaceTypeSerializer(source="type")
creator = PlaceUserSerializer(source="created_by")
metaplace_data = PlaceMetaplaceSerializer(source="metaplace")
# total_sessions = SerializerMethodField()
# last_session = SerializerMethodField()
# contact = SerializerMethodField()
class Meta:
model = Place
......@@ -88,51 +82,6 @@ class PlaceSerializer(serializers.GeoFeatureModelSerializer):
"geom",
"creator",
"timestamp_create",
# "total_sessions",
# "last_session",
# "contact",
)
# def get_total_sessions(self, place):
# session = Session.objects.filter(place=place.id_place)
# return session.count()
# def get_last_session(self, place):
# session = Session.objects.filter(place=place.id_place)
# return session.aggregate(Max("date_start"))
# def get_contact(self, place):
# session = Session.objects.filter(place=place.id_place)
# return session.aggregate(
# contacts=StringAgg("contact__descr", delimiter=", ", distinct=True)
# )
class PlaceBboxSerializer(serializers.GeoFeatureModelSerializer):
municipality_data = PlaceMunicipalitySerializer(source="municipality")
type_data = PlaceTypeSerializer(source="type")
creator = PlaceUserSerializer(source="created_by")
metaplace_data = PlaceMetaplaceSerializer(source="metaplace")
class Meta:
model = Place
depth = 1
geo_field = "geom"
fields = (
"id_place",
"name",
"altitude",
"municipality_data",
"metaplace_data",
"type_data",
"is_managed",
"convention",
"geom",
"creator",
"timestamp_create",
"total_sessions",
"last_session",
"contact",
)
......
......@@ -341,7 +341,7 @@ Observations de la session {{object.name}}
<div v-if="form_fields.includes('gland_coul')" class="form-group">
<label class="control-label" for="GlandCoul">Couleur des glandes salivaires</label>
<dict-select :options="filteredDict(dicts.BiomGlandCoul)" v-model="cd.gland_coul"/>
</div>
</div>form-inline
<div v-if="form_fields.includes('gland_taille')" class="form-group">
<label class="control-label" for="GlandTaille">Taille des glandes salivaires</label>
<dict-select :options="filteredDict(dicts.BiomGlandTaille)" v-model="cd.gland_taille"/>
......@@ -370,11 +370,20 @@ Observations de la session {{object.name}}
</div>
<span @click="moreCountDetailForm = !moreCountDetailForm"><i class="fas fa-2x"
:class="moreCountDetailForm ? 'fa-caret-up ':'fa-caret-down'"></i></span>
<div id="moreCountDetailForm" v-show="moreCountDetailForm">
<div class="form-group" v-if="form_fields.includes('comment')">
<label for="cdComment" class="control-label">Commentaire</label>
<textarea id="cdComment" class="form-control" placeholder="Commentaire"
v-model="cd.comment" class="form-control" rows="3"></textarea>
<div id="moreCountDetailForm" v-if="moreCountDetailForm">
<div class="checkbox form-inline">
<label class="control-label" for="determiner">Déterminateur</label>
<user-select-one id="determiner" v-model="cd.manipulator"/>
</div>
<div class="checkbox form-inline">
<label class="control-label" for="determiner">Validateur</label>
<user-select-one id="determiner" v-model="cd.validator"/>
</div>
<div class="form-group">
<label for="cdComment">Commentaire</label>
<textarea id="cdComment" placeholder="Commentaire"
v-model:title="cd.comment" v-model:content="cd.comment"
class="form-control" rows="3"></textarea>
</div>
</div>
<div class="btn btn-danger btn-sm pull-right" @click="delCountDetail(selectedItem,icd)" tooltip><i
......
{% extends 'base_fullwidth.html' %} {% load i18n static %} {% block title %}
Observations de la session {{object.name}} {% endblock %} {% block content %}
<style>
body{
display: flex;
flex-direction: column;
}
main {
flex:1;
width: 100%;
background-color:red;
}
main > * {
display:flex;
}
.mainBlock {
display: flex;
flex-flow: row wrap;
flex:1;
height: 100%;
width: 100%;
font-weight: bold;
text-align: center;
background-color: grey;
}
.mainBlock > * {
padding: 10px;
flex: 1 100%;
}
.content {
height: 100%;
display: flex;
{% comment %} background: pink; {% endcomment %}
}
.titleBlock{
background-color: blue;
display: block;
}
.speciesBlock{
{% comment %} flex:1; {% endcomment %}
background-color: green;
{% comment %} flex: 1 0 0; {% endcomment %}
overflow-y: auto;
flex-direction: column !important;
display: flex;
}
.detailBlock{
{% comment %} content-items: stretch; {% endcomment %}
background-color: yellow;
align-content: stretch !important;
flex-direction: column !important;
flex-grow: 1 !important;
display: flex !important;
}
</style>
<div class="mainBlock">
<div class="titleBlock col-12">
<h1>title</h1>
</div>
<div class="content">
<div class="speciesBlock col-xs-3">
<div class="half"><h4>Top left</h4></div>
<div class="half"><h4>Bottom left</h4></div>
</div>
<div class="detailBlock col-xs-9">
<h4>Extra step: center stuff here</h4>
</div>
</div>
</div>
{% endblock %}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment