Commit 48b386c0 authored by hypsug0's avatar hypsug0
Browse files

wip: sighting/countdetail dynamic form

parent 5c1cea46
......@@ -6,7 +6,7 @@ html {
body {
/*padding-top: 30px;*/
padding-top: 0px;
font-family: "LatoRegular", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-family: 'LatoRegular', 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 14px;
font-weight: 400;
line-height: 1.42857143;
......@@ -14,8 +14,12 @@ body {
background-color: #ecf0f1;
}
h1, h2, h3, h4, h5 {
font-family: "Lato";
h1,
h2,
h3,
h4,
h5 {
font-family: 'Lato';
}
/* Sticky footer styles
......@@ -49,7 +53,7 @@ header {
}
header h1 {
font-family: "Lato";
font-family: 'Lato';
}
.footer {
......@@ -63,7 +67,7 @@ header h1 {
padding: 10px;
background-color: #1a1a1a;
color: #eeeeee;
box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
}
/* Custom page CSS
......@@ -89,7 +93,7 @@ header h1 {
text-align: center;
color: #fff;
height: 170px;
box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
}
.page-header h1 {
......@@ -110,7 +114,7 @@ header h1 {
text-align: center;
color: #fff;
height: 100px;
box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
}
.page-header h1 {
......@@ -219,14 +223,63 @@ table {
border-top: 1px solid #ecf0f1;
}
thead, tbody, tr {
thead,
tbody,
tr {
border-bottom: 1px solid #ecf0f1;
}
/*
BS Grid
*/
.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 {
.col-xs-1,
.col-sm-1,
.col-md-1,
.col-lg-1,
.col-xs-2,
.col-sm-2,
.col-md-2,
.col-lg-2,
.col-xs-3,
.col-sm-3,
.col-md-3,
.col-lg-3,
.col-xs-4,
.col-sm-4,
.col-md-4,
.col-lg-4,
.col-xs-5,
.col-sm-5,
.col-md-5,
.col-lg-5,
.col-xs-6,
.col-sm-6,
.col-md-6,
.col-lg-6,
.col-xs-7,
.col-sm-7,
.col-md-7,
.col-lg-7,
.col-xs-8,
.col-sm-8,
.col-md-8,
.col-lg-8,
.col-xs-9,
.col-sm-9,
.col-md-9,
.col-lg-9,
.col-xs-10,
.col-sm-10,
.col-md-10,
.col-lg-10,
.col-xs-11,
.col-sm-11,
.col-md-11,
.col-lg-11,
.col-xs-12,
.col-sm-12,
.col-md-12,
.col-lg-12 {
padding: 10px;
}
......@@ -323,7 +376,7 @@ BS Full screen Modal
.navbar {
/*min-height: 40px;*/
/*margin-bottom: 15px;*/
box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
border-right: #eee 1px solid;
}
......@@ -390,11 +443,10 @@ BS Full screen Modal
height: 100%;
width: 3px;
/*background-color: #1c1c1c;*/
-webkit-transition: width .2s ease-in;
-moz-transition: width .2s ease-in;
-ms-transition: width .2s ease-in;
transition: width .2s ease-in;
-webkit-transition: width 0.2s ease-in;
-moz-transition: width 0.2s ease-in;
-ms-transition: width 0.2s ease-in;
transition: width 0.2s ease-in;
}
.sidebar-nav li:first-child a {
......@@ -405,10 +457,10 @@ BS Full screen Modal
.sidebar-nav li:hover:before,
.sidebar-nav li.open:hover:before {
width: 100%;
-webkit-transition: width .2s ease-in;
-moz-transition: width .2s ease-in;
-ms-transition: width .2s ease-in;
transition: width .2s ease-in;
-webkit-transition: width 0.2s ease-in;
-moz-transition: width 0.2s ease-in;
-ms-transition: width 0.2s ease-in;
transition: width 0.2s ease-in;
color: #fff;
}
......@@ -511,14 +563,14 @@ BS Full screen Modal
text-align: center;
opacity: 0;
-webkit-transform: translate3d(0, 0, 0);
-webkit-transition: all .35s ease-in-out;
-webkit-transition: all 0.35s ease-in-out;
}
.hamburger.is-closed:hover:before {
opacity: 1;
display: block;
-webkit-transform: translate3d(-100px, 0, 0);
-webkit-transition: all .35s ease-in-out;
-webkit-transition: all 0.35s ease-in-out;
}
.hamburger.is-closed .hamb-top,
......@@ -541,7 +593,7 @@ BS Full screen Modal
.hamburger.is-closed .hamb-top {
top: 5px;
-webkit-transition: all .35s ease-in-out;
-webkit-transition: all 0.35s ease-in-out;
background-color: #8c8c8c;
}
......@@ -553,17 +605,17 @@ BS Full screen Modal
.hamburger.is-closed .hamb-bottom {
bottom: 5px;
-webkit-transition: all .35s ease-in-out;
-webkit-transition: all 0.35s ease-in-out;
}
.hamburger.is-closed:hover .hamb-top {
top: 0;
-webkit-transition: all .35s ease-in-out;
-webkit-transition: all 0.35s ease-in-out;
}
.hamburger.is-closed:hover .hamb-bottom {
bottom: 0;
-webkit-transition: all .35s ease-in-out;
-webkit-transition: all 0.35s ease-in-out;
}
.hamburger.is-open .hamb-top,
......@@ -580,7 +632,7 @@ BS Full screen Modal
.hamburger.is-open .hamb-top {
-webkit-transform: rotate(45deg);
-webkit-transition: -webkit-transform .2s cubic-bezier(.73, 1, .28, .08);
-webkit-transition: -webkit-transform 0.2s cubic-bezier(0.73, 1, 0.28, 0.08);
}
.hamburger.is-open .hamb-middle {
......@@ -589,7 +641,7 @@ BS Full screen Modal
.hamburger.is-open .hamb-bottom {
-webkit-transform: rotate(-45deg);
-webkit-transition: -webkit-transform .2s cubic-bezier(.73, 1, .28, .08);
-webkit-transition: -webkit-transform 0.2s cubic-bezier(0.73, 1, 0.28, 0.08);
}
.hamburger.is-open:before {
......@@ -602,14 +654,14 @@ BS Full screen Modal
text-align: center;
opacity: 0;
-webkit-transform: translate3d(0, 0, 0);
-webkit-transition: all .35s ease-in-out;
-webkit-transition: all 0.35s ease-in-out;
}
.hamburger.is-open:hover:before {
opacity: 1;
display: block;
-webkit-transform: translate3d(-100px, 0, 0);
-webkit-transition: all .35s ease-in-out;
-webkit-transition: all 0.35s ease-in-out;
}
/*-------------------------------*/
......@@ -625,7 +677,7 @@ BS Full screen Modal
left: 0;
right: 0;
bottom: 0;
background-color: rgba(250, 250, 250, .8);
background-color: rgba(250, 250, 250, 0.8);
z-index: 1;
}
......@@ -659,12 +711,13 @@ BS Full screen Modal
/*border-radius: 0px;*/
height: 26px;
font-size: 1.25em;
padding: 0 .125em;
padding: 0 0.125em;
margin: 3px;
padding-right: 30px
padding-right: 30px;
}
.leaflet-control-search .search-button, .leaflet-control-search .search-button:hover {
.leaflet-control-search .search-button,
.leaflet-control-search .search-button:hover {
background-image: url(images/search_icons.png);
-webkit-border-radius: 4px;
border-radius: 4px;
......@@ -674,18 +727,16 @@ BS Full screen Modal
}
.leaflet-control-search.search-load .search-input {
background: url(images/loader.gif) no-repeat center right #fff
background: url(images/loader.gif) no-repeat center right #fff;
}
.zoomInfo {
font-size: 1.5rem;
}
.legend {
padding: 6px 8px;
font: 14px "LatoRegular", "Helvetica Neue", Helvetica, Arial, sans-serif;
font: 14px 'LatoRegular', 'Helvetica Neue', Helvetica, Arial, sans-serif;
background: white;
background: rgba(255, 255, 255, 0.8);
box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
......@@ -708,12 +759,10 @@ BS Full screen Modal
border-radius: 50%;
}
.form-inline > * {
margin: 10px 3px !important;
}
#spinner {
margin: auto;
position: absolute;
......@@ -726,16 +775,16 @@ BS Full screen Modal
height: 40px;
background-color: #333;
border-radius: 100%;
-webkit-animation: sk-scaleout 1.0s infinite ease-in-out;
animation: sk-scaleout 1.0s infinite ease-in-out;
-webkit-animation: sk-scaleout 1s infinite ease-in-out;
animation: sk-scaleout 1s infinite ease-in-out;
}
@-webkit-keyframes sk-scaleout {
0% {
-webkit-transform: scale(0)
-webkit-transform: scale(0);
}
100% {
-webkit-transform: scale(1.0);
-webkit-transform: scale(1);
opacity: 0;
}
}
......@@ -746,8 +795,24 @@ BS Full screen Modal
transform: scale(0);
}
100% {
-webkit-transform: scale(1.0);
transform: scale(1.0);
-webkit-transform: scale(1);
transform: scale(1);
opacity: 0;
}
}
#sighting-form {
margin: 5px;
padding: 5px;
border: 1px solid #ccc;
background-color: whitesmoke;
border-radius: 5px;
}
#countdetail-form {
margin: 5px;
padding: 5px;
border: 1px solid #ccc;
background-color: white;
border-radius: 5px;
}
......@@ -57,7 +57,7 @@ const addSpinner = () => {
$('#main').append("<div id='spinner'></div>")
}
var displaySpinner = function (dataState) {
const displaySpinner = (dataState) => {
const spinner = $('#spinner')
if (!dataState) {
spinner.hide()
......
......@@ -87,6 +87,7 @@ urlpatterns = [
path("", include("blog.urls", namespace="blog")),
path("", include("sights.urls", namespace="sights")),
path("", include("management.urls", namespace="management")),
path("", include("dicts.urls", namespace="dicts")),
# path("", include("metadata.urls", namespace="metadata")),
path("synthesis/", include("synthesis.urls", namespace="synthesis")),
path("api/", include("api.urls", namespace="api")),
......
import json
from django.apps import apps
from django.http import Http404, HttpResponse
def apiDicts(req):
"""List all values from all dicts in one single request"""
dicts = {}
app_models = apps.get_app_config("dicts").get_models()
for model in app_models:
name = model.__name__
values = model.objects.all().values()
list_values = [entry for entry in values]
dicts[name] = list_values
jsondicts = json.dumps(dicts)
return HttpResponse(jsondicts, content_type="application/json")
def apiDict(req, model):
"""List all values from all dicts in one single request"""
try:
model = apps.get_model(".".join(["dicts", model]))
print("MODEL", model)
values = model.objects.all().values()
list_values = [entry for entry in values]
jsondicts = json.dumps(list_values)
return HttpResponse(jsondicts, content_type="application/json")
except Exception as e:
print("ERROR", e)
raise Http404
# return HttpResponse({"error": e}, status=500, content_type="application/json")
from django.urls import path
from dicts.api import apiDict, apiDicts
app_name = "dicts"
urlpatterns = [
# url de recherches de localités
path("api/v1/dicts", apiDicts, name="api_dicts"),
path("api/v1/dict/<str:model>", apiDict, name="api_dict_by_model"),
]
......@@ -10,8 +10,12 @@ from rest_framework import generics
from rest_framework.viewsets import ModelViewSet
from sights.mixins import LargeGeoJsonPageNumberPagination, SightingFilteredListWithPermissions
from sights.models import Sighting
from sights.observation.serializers import EditSightingSerializer, SightingSerializer
from sights.models import CountDetail, Sighting
from sights.observation.serializers import (
CountDetailSerializer,
EditSightingSerializer,
SightingSerializer,
)
# Get an instance of a logger
logger = logging.getLogger(__name__)
......@@ -65,3 +69,29 @@ class EditSighting(ModelViewSet):
# serializer = UserSerializer(user)
# return Response(serializer.data)
# return Response(obs)
class SessionSightingsListView(generics.ListCreateAPIView):
serializer_class = EditSightingSerializer
def get_queryset(self):
session = self.kwargs["session"]
return Sighting.objects.filter(session=session)
class SessionSightingsView(generics.RetrieveUpdateDestroyAPIView):
serializer_class = EditSightingSerializer
def get_queryset(self):
session = self.kwargs["session"]
return Sighting.objects.filter(session=session)
class SightingCountDetailsListView(generics.ListCreateAPIView):
queryset = CountDetail.objects.all()
serializer_class = CountDetailSerializer
class SightingCountDetailsView(generics.RetrieveUpdateDestroyAPIView):
queryset = CountDetail.objects.all()
serializer_class = CountDetailSerializer
......@@ -90,6 +90,33 @@ class SightingSerializer(serializers.GeoFeatureModelSerializer):
# return countdetails
class EditSightingListSerializer(serializers.ListSerializer):
def create(self, validated_data):
sightings = [Sighting(**item) for item in validated_data]
return Sighting.objects.bulk_create(sightings)
# def update(self, instance, validated_data):
# # Maps for id->instance and id->data item.
# book_mapping = {book.id: book for book in instance}
# data_mapping = {item["id"]: item for item in validated_data}
# # Perform creations and updates.
# ret = []
# for book_id, data in data_mapping.items():
# book = book_mapping.get(book_id, None)
# if book is None:
# ret.append(self.child.create(data))
# else:
# ret.append(self.child.update(book, data))
# # Perform deletions.
# for book_id, book in book_mapping.items():
# if book_id not in data_mapping:
# book.delete()
# return ret
class EditSightingSerializer(serializers.ModelSerializer):
"""Sighting serializer for editing data (insert/update)
from sighting and count details model"""
......@@ -100,6 +127,7 @@ class EditSightingSerializer(serializers.ModelSerializer):
model = Sighting
fields = (
"id_sighting",
"session",
"codesp",
"breed_colo",
"total_count",
......@@ -109,6 +137,7 @@ class EditSightingSerializer(serializers.ModelSerializer):
"extra_data",
"countdetail_sighting",
)
list_serializer_class = EditSightingListSerializer
def get_countdetail_sighting(self, sighting):
"""Retrieve all child count details
......@@ -121,3 +150,27 @@ class EditSightingSerializer(serializers.ModelSerializer):
"""
countdetails = CountDetail.objects.filter(sighting=sighting.id_sighting).all()
return countdetails
def create(self, validated_data):
countdetails = validated_data.pop("countdetail_sighting")
sighting = Sighting.objects.create(**validated_data)
for countdetail in countdetails:
CountDetail.objects.create(sighting=sighting, **countdetail)
return sighting
# def update(self, instance, validated_data):
# albums_data = validated_data.pop("album_musician")
# albums = (instance.album_musician).all()
# albums = list(albums)
# instance.first_name = validated_data.get("first_name", instance.first_name)
# instance.last_name = validated_data.get("last_name", instance.last_name)
# instance.instrument = validated_data.get("instrument", instance.instrument)
# instance.save()
# for album_data in albums_data:
# album = albums.pop(0)
# album.name = album_data.get("name", album.name)
# album.release_date = album_data.get("release_date", album.release_date)
# album.num_stars = album_data.get("num_stars", album.num_stars)
# album.save()
# return instance
from django.urls import path
from sights.observation.api import EditSighting, GeoJSONSighting
from sights.observation.api import GeoJSONSighting, SessionSightingsListView
from sights.observation.views import (
SightingCreate,
SightingDelete,
......@@ -24,7 +24,7 @@ urlpatterns = [
path("session/<int:pk>/sighting/edit", SightingEditForm.as_view(), name="sighting_edit_form"),
path(
"api/v1/session/<int:session>/sightings",
EditSighting.as_view({"get": "list"}),
SessionSightingsListView.as_view(),
name="sighting_detail_api",
),
path("sighting/search", SightingSearch.as_view(), name="sighting_search"),
......
......@@ -64,18 +64,6 @@
var metaplaceBaseUrl = '/metaplace/api/search';
function showDisclaimer() {
var div = document.getElementById("info legend")
div.innerHTML = '<i style="background: #ff4c41"></i><span>Localisation simple</span><br/>';
div.innerHTML += '<i style="background: #8A2BE2"></i><span>Localisation sur un métasite</span><br/>';
div.innerHTML += '<i style="background: #9370DB; border-radius: 0%;"></i><span>Emprise du métasite</span><br/>';
}
function hideDisclaimer() {
var div = document.getElementById("info legend")
div.innerHTML = "<b class='fa fa-info-circle'></b>";
}
window.addEventListener("map:init", function (event) {
var markers = L.markerClusterGroup({
chunkedLoading: true,
......
......@@ -82,18 +82,7 @@
var placeBaseUrl = '{% url 'sights:place_bbox_search_api' %}';
var metaplaceBaseUrl = '{% url 'sights:metaplace_bbox_search_api' %}';
function showDisclaimer() {
var div = document.getElementById("info legend")
div.innerHTML = '<i style="background: #ff4c41"></i><span>Localisation simple</span><br/>';
div.innerHTML += '<i style="background: #8A2BE2"></i><span>Localisation sur un métasite</span><br/>';
div.innerHTML += '<i style="background: #9370DB; border-radius: 0%;"></i><span>Emprise du métasite</span><br/>';
}
function hideDisclaimer() {
var div = document.getElementById("info legend")
div.innerHTML = "<b class='fa fa-info-circle'></b>";
}
window.addEventListener("map:init", function (event) {
var placeMarkers = L.markerClusterGroup({
chunkedLoading: true,
......
{% extends 'base_fullwidth_leaflet.html' %}
{% load i18n static %}
{% extends 'base_fullwidth_leaflet.html' %}
{% load i18n static %}
{% block content %}
<script src="{% static 'js/vue.js' %}"></script>
<h1><i class="{{ icon | safe }}"></i> {{ title | safe }}</h1>
<div id="sightingForm" >
<span class="label label-default">[[data.results?.length]]</span>
<div v-for='r in data.results'>
<div ><pre>[[r]]</pre></div>
<div style="margin-left: 50px" v-for='cd in r.countdetail_sighting'><pre>[[cd]]</pre></div>
{% comment %} <script src="https://unpkg.com/vue-select@latest"></script>
<link rel="stylesheet" href="https://unpkg.com/vue-select@latest/dist/vue-select.css" /> {% endcomment %}
<div id="sessionSightingEdit">
{{ object.pk}}
<h1><i class="{{ icon | safe }}"></i> {{ title | safe }} <span v-if="initialDataLoaded"
class="pull-right label label-success label-sm">[[data.length]] obs</span></h1>
<div style="height: 500px" v-if="!(initialDataLoaded && dictsLoaded)">
<div align="center">Chargement en cours...</div>
<div id="spinner"></div>
</div>
<div v-else>
<form id="sighting-form" class="form-inline" v-for="(r, is) in data">
<h4>[[ getSciName(r.codesp) ]]</h4>
<span v-if="r.id_sighting" class="label label-default">#[[r.id_sighting]]</span>