Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
dbchiro
dbchiroweb
Commits
50991e66
Commit
50991e66
authored
Apr 22, 2021
by
hypsug0
Browse files
feat: WIP on studies
parent
49aa0832
Changes
13
Hide whitespace changes
Inline
Side-by-side
core/static/css/app.css
View file @
50991e66
...
...
@@ -953,7 +953,7 @@ BS Full screen Modal
}
.statBlock
.statCount
{
font-size
:
2
em
;
font-size
:
1.5
em
;
}
.statBlock
.statDesc
{
...
...
core/static/js/app.js
View file @
50991e66
// // For Bootstrap dropdown-submenu
// (function ($) {
// $(document).ready(function () {
...
...
@@ -1849,8 +1848,15 @@ Vue.component('session-group-item', {
</div>`
,
})
const
studyDataApp
=
(
pk
)
=>
{
Vue
.
component
(
'
pie-chart
'
,
{
extends
:
VueChartJs
.
Pie
,
props
:
[
'
chartData
'
,
'
options
'
],
mounted
()
{
this
.
renderChart
(
this
.
chartData
,
this
.
options
)
},
})
const
studyDataApp
=
(
pk
)
=>
{
// Vue.component('pie-chart', {
// extends: Pie,
// data() {
...
...
@@ -1887,14 +1893,23 @@ const studyDataApp = (pk) => {
dataSource
:
'
stats
'
,
//sessions: [],
loading
:
true
,
contactSessionsChartOptions
:
{
contactChartDict
:
{
vv
:
{
label
:
'
Contact visuel
'
,
color
:
'
#e89bda
'
},
du
:
{
label
:
'
Contact acoustique
'
,
color
:
'
#7ed6a0
'
},
vm
:
{
label
:
'
Vu en main
'
,
color
:
'
#c6c63d
'
},
te
:
{
label
:
'
Contact télémétrique
'
,
color
:
'
#517eef
'
},
nc
:
{
label
:
'
Non communiqué
'
,
color
:
'
#aaaaaa
'
},
},
contactChartOptions
:
{
options
:
{
parsing
:
{
xAxisKey
:
'
label
'
,
yAxisKey
:
'
value
'
,
},
},
}
},
dicts
:
{},
dictSpecies
:
{},
}
},
methods
:
{
...
...
@@ -1909,6 +1924,12 @@ const studyDataApp = (pk) => {
})
.
catch
((
error
)
=>
console
.
error
(
error
))
},
getDicts
()
{
this
.
dicts
=
JSON
.
parse
(
localStorage
.
getItem
(
'
dicts
'
))
this
.
dictSpecies
=
JSON
.
parse
(
localStorage
.
getItem
(
'
dictSpecies
'
)
)
},
countPlaces
()
{
return
this
.
sightingData
.
length
},
...
...
@@ -1946,22 +1967,107 @@ const studyDataApp = (pk) => {
},
contacts
()
{
const
contacts
=
[
...
new
Set
(
this
.
sessions
.
map
((
e
)
=>
e
.
contact
.
de
scr
)),
...
new
Set
(
this
.
sessions
.
map
((
e
)
=>
e
.
contact
.
co
de
)),
]
console
.
log
(
'
contacts
'
,
contacts
)
const
data
=
[]
contacts
.
forEach
((
e
)
=>
{
data
.
push
({
label
:
e
,
code
:
e
,
value
:
this
.
sessions
.
filter
(
(
se
)
=>
se
.
contact
.
de
scr
==
e
(
se
)
=>
se
.
contact
.
co
de
==
e
).
length
,
})
})
return
data
},
taxa
()
{
const
sightings
=
this
.
sessions
.
map
((
e
)
=>
e
.
observations
)
.
flat
()
console
.
log
(
sightings
)
const
listTaxa
=
[
...
new
Set
(
sightings
.
map
((
e
)
=>
e
.
codesp
.
codesp
)
),
]
const
taxa
=
[]
listTaxa
.
forEach
((
e
)
=>
{
taxa
.
push
({
code
:
e
,
common_name
:
this
.
dictSpecies
.
find
(
sp
=>
sp
.
codesp
==
e
).
common_name_fr
,
sci_name
:
this
.
dictSpecies
.
find
(
sp
=>
sp
.
codesp
==
e
).
sci_name
,
sp_true
:
this
.
dictSpecies
.
find
(
sp
=>
sp
.
codesp
==
e
).
sp_true
,
value
:
sightings
.
filter
(
(
se
)
=>
se
.
codesp
.
codesp
==
e
&&
se
.
total_count
>
0
).
length
,
})
})
return
taxa
.
sort
((
a
,
b
)
=>
b
.
value
-
a
.
value
)
},
main_observers
()
{
const
main_observers
=
this
.
sessions
.
map
((
e
)
=>
e
.
main_observer
)
.
flat
()
return
[...
new
Set
(
main_observers
.
map
(
e
=>
e
.
get_full_name
))]
},
chartData
()
{
return
{
labels
:
[
...
this
.
contacts
.
map
(
(
e
)
=>
this
.
contactChartDict
[
e
.
code
].
label
),
],
datasets
:
[
{
label
:
'
ds1
'
,
backgroundColor
:
[
...
this
.
contacts
.
map
(
(
e
)
=>
this
.
contactChartDict
[
e
.
code
].
color
||
'
#ef517b
'
),
],
data
:
[...
this
.
contacts
.
map
((
e
)
=>
e
.
value
)],
},
],
}
},
},
mounted
()
{
this
.
getSightings
()
this
.
getSightings
(),
this
.
getDicts
()
},
}
}
const
studyListApp
=
()
=>
{
return
{
el
:
'
#studyList
'
,
delimiters
:
[
'
[[
'
,
'
]]
'
],
data
()
{
return
{
loading
:
true
,
studyList
:
[],
}
},
methods
:
{
getStudies
()
{
$http
.
get
(
`/api/v1/study/list?format=json`
)
.
then
((
r
)
=>
{
console
.
debug
(
r
)
this
.
studyList
=
r
.
data
.
results
this
.
loading
=
false
})
.
catch
((
error
)
=>
console
.
error
(
error
))
},
},
mounted
()
{
this
.
getStudies
()
}
}
}
core/static/js/preload_app.js
View file @
50991e66
...
...
@@ -50,7 +50,7 @@ const _debounce = (func, timeout = 300) => {
const
storeDicts
=
()
=>
{
// if (localStorage.getItem('dicts') === null) {
if
(
true
)
{
if
(
!
localStorage
.
hasOwnProperty
(
'
dicts
'
)
)
{
$http
.
get
(
'
/api/v1/dicts
'
)
.
then
((
response
)
=>
{
...
...
management/mixins.py
View file @
50991e66
...
...
@@ -47,3 +47,26 @@ class CatchAuthCreateAuthMixin:
return
super
(
CatchAuthCreateAuthMixin
,
self
).
dispatch
(
request
,
*
args
,
**
kwargs
)
else
:
return
redirect
(
"core:update_unauth"
)
class
StudyAuthMixin
:
"""
Classe mixin de vérification que l'utilisateur possède les droits de créer le compte
"""
def
dispatch
(
self
,
request
,
*
args
,
**
kwargs
):
if
not
request
.
user
.
is_authenticated
:
return
redirect
(
"%s?next=%s"
%
(
reverse
(
"auth_login"
),
request
.
path
))
else
:
loggeduser
=
get_user_model
().
objects
.
get
(
id
=
request
.
user
.
id
)
if
(
loggeduser
.
edit_all_data
or
loggeduser
.
access_all_data
or
loggeduser
.
is_resp
or
loggeduser
.
is_staff
or
loggeduser
.
is_superuser
or
self
.
obj
.
project_manager
==
loggeduser
):
return
super
(
StudyAuthMixin
,
self
).
dispatch
(
request
,
*
args
,
**
kwargs
)
else
:
return
redirect
(
"core:view_unauth"
)
management/study/api.py
View file @
50991e66
...
...
@@ -8,12 +8,13 @@ from sights.models import Place, Session
from
sights.observation.api
import
GeoJSONSighting
from
..models
import
Study
from
.serializers
import
StudyDataSerializer
from
.permissions
import
StudyDetailPermission
from
.serializers
import
StudyDataSerializer
,
StudySerializer
logger
=
logging
.
getLogger
(
__name__
)
class
StudySightings
(
GeoJSONSighting
):
class
StudySightings
Api
(
GeoJSONSighting
):
pagination_class
=
None
def
get_queryset
(
self
,
*
args
,
**
kwargs
):
...
...
@@ -22,7 +23,7 @@ class StudySightings(GeoJSONSighting):
return
qs
.
distinct
().
order_by
(
"-timestamp_update"
)
class
StudyDatas
(
ListAPIView
):
class
StudyDatas
Api
(
ListAPIView
):
serializer_class
=
StudyDataSerializer
queryset
=
(
Place
.
objects
.
select_related
(
"created_by"
)
...
...
@@ -34,6 +35,7 @@ class StudyDatas(ListAPIView):
.
prefetch_related
(
Prefetch
(
"sessions__main_observer"
))
# .prefetch_related(Prefetch("sessions__other_observer"))
)
permission_class
=
StudyDetailPermission
def
get_queryset
(
self
,
*
args
,
**
kwargs
):
# qs = super().get_queryset()7
...
...
@@ -63,3 +65,15 @@ class StudyDatas(ListAPIView):
qs
=
qs
.
filter
(
sessions__study
=
pk
)
logger
.
debug
(
f
"QS TEST
{
qs
.
query
}
"
)
return
qs
.
distinct
().
order_by
(
"-timestamp_update"
).
all
()
class
StudyListApi
(
ListAPIView
):
serializer_class
=
StudySerializer
queryset
=
(
Study
.
objects
.
select_related
(
"created_by"
)
.
select_related
(
"updated_by"
)
.
select_related
(
"project_manager"
)
.
prefetch_related
(
"sessions"
)
.
order_by
(
"timestamp_update"
)
# .prefetch_related(Prefetch("sessions__other_observer"))
)
management/study/mixins.py
0 → 100644
View file @
50991e66
"""
Study Mixins
"""
from
django.shortcuts
import
redirect
from
django.urls
import
reverse
class
StudyAuthMixin
:
"""
Classe mixin de vérification que l'utilisateur possède les droits de créer le compte
"""
def
dispatch
(
self
,
request
,
*
args
,
**
kwargs
):
if
not
request
.
user
.
is_authenticated
:
return
redirect
(
"%s?next=%s"
%
(
reverse
(
"auth_login"
),
request
.
path
))
else
:
loggeduser
=
request
.
user
if
(
loggeduser
.
edit_all_data
or
loggeduser
.
access_all_data
or
loggeduser
.
is_resp
or
loggeduser
.
is_staff
or
loggeduser
.
is_superuser
or
self
.
obj
.
project_manager
==
loggeduser
):
return
super
(
StudyAuthMixin
,
self
).
dispatch
(
request
,
*
args
,
**
kwargs
)
else
:
return
redirect
(
"core:view_unauth"
)
management/study/permissions.py
0 → 100644
View file @
50991e66
from
rest_framework.permissions
import
SAFE_METHODS
,
BasePermission
class
StudyDetailPermission
(
BasePermission
):
"""
Object-level permission to only allow owners of an object to edit it.
Assumes the model instance has an `owner` attribute.
"""
def
has_object_permission
(
self
,
request
,
view
,
obj
):
# Read permissions are allowed to any request,
# so we'll always allow GET, HEAD or OPTIONS requests.
if
request
.
user
:
loggeduser
=
request
.
user
# Instance must have an attribute named `owner`.
return
(
loggeduser
.
edit_all_data
or
loggeduser
.
access_all_data
or
loggeduser
.
is_resp
or
loggeduser
.
is_staff
or
loggeduser
.
is_superuser
or
obj
.
project_manager
==
loggeduser
)
management/study/serializers.py
View file @
50991e66
...
...
@@ -7,6 +7,8 @@ from accounts.serializer import ProfileSearchSerializer
from
sights.models
import
Place
,
Session
,
Sighting
from
sights.observation.serializers
import
ContactSerializer
from
..models
import
Study
logger
=
logging
.
getLogger
(
__name__
)
...
...
@@ -53,3 +55,14 @@ class StudyDataSerializer(GeoFeatureModelSerializer):
# def get_sessions(self, instance):
# queryset = instance.sessions.order_by("-date_start")
# return SessionSerializer(queryset, many=True).data
class
StudySerializer
(
ModelSerializer
):
project_manager
=
ProfileSearchSerializer
()
updated_by
=
ProfileSearchSerializer
()
created_by
=
ProfileSearchSerializer
()
class
Meta
:
model
=
Study
depth
=
1
fields
=
"__all__"
management/study/views.py
View file @
50991e66
...
...
@@ -7,7 +7,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin
from
django.db.models
import
Prefetch
from
django.urls
import
reverse_lazy
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.views.generic
import
DetailView
from
django.views.generic
import
DetailView
,
TemplateView
from
django.views.generic.edit
import
CreateView
,
DeleteView
,
UpdateView
from
django_tables2
import
SingleTableView
...
...
@@ -15,6 +15,7 @@ from ..forms import StudyForm
from
..mixins
import
ManagementAuthMixin
from
..models
import
Study
from
..tables
import
StudyTable
from
.mixins
import
StudyAuthMixin
class
StudyCreate
(
LoginRequiredMixin
,
CreateView
):
...
...
@@ -72,11 +73,11 @@ class StudyDelete(ManagementAuthMixin, DeleteView):
return
context
class
StudyList
(
LoginRequiredMixin
,
SingleTabl
eView
):
table_class
=
StudyTable
model
=
Study
template_name
=
"
table
.html"
table_pagination
=
{
"per_page"
:
25
}
class
StudyList
(
LoginRequiredMixin
,
Templat
eView
):
#
table_class = StudyTable
#
model = Study
template_name
=
"
study_list
.html"
#
table_pagination = {"per_page": 25}
def
get_context_data
(
self
,
**
kwargs
):
context
=
super
(
StudyList
,
self
).
get_context_data
(
**
kwargs
)
...
...
@@ -89,19 +90,20 @@ class StudyList(LoginRequiredMixin, SingleTableView):
return
context
class
StudyDetail
(
LoginRequiredMixin
,
DetailView
):
queryset
=
(
Study
.
objects
.
select_related
(
"project_manager"
)
.
select_related
(
"created_by"
)
.
select_related
(
"updated_by"
)
.
prefetch_related
(
"sessions"
)
.
prefetch_related
(
Prefetch
(
"sessions__observations"
))
.
prefetch_related
(
Prefetch
(
"sessions__place"
))
.
prefetch_related
(
Prefetch
(
"sessions__contact"
))
.
prefetch_related
(
Prefetch
(
"sessions__observations__codesp"
))
.
prefetch_related
(
Prefetch
(
"sessions__main_observer"
))
.
prefetch_related
(
Prefetch
(
"sessions__other_observer"
))
)
class
StudyDetail
(
LoginRequiredMixin
,
StudyAuthMixin
,
DetailView
):
# queryset = (
# Study.objects.select_related("project_manager")
# .select_related("created_by")
# .select_related("updated_by")
# .prefetch_related("sessions")
# .prefetch_related(Prefetch("sessions__observations"))
# .prefetch_related(Prefetch("sessions__place"))
# .prefetch_related(Prefetch("sessions__contact"))
# .prefetch_related(Prefetch("sessions__observations__codesp"))
# .prefetch_related(Prefetch("sessions__main_observer"))
# .prefetch_related(Prefetch("sessions__other_observer"))
# )
model
=
Study
template_name
=
"study_detail.html"
def
get_context_data
(
self
,
**
kwargs
):
...
...
management/templates/study_detail.html
View file @
50991e66
...
...
@@ -68,7 +68,8 @@
<div
class=
"panel-heading"
>
<h4
class=
"panel-title"
>
<select
class=
"pull-right input-sm"
v-model=
"dataSource"
>
<option
value=
"stats"
>
Données générales
</option>
<option
value=
"stats"
default
>
Données générales
</option>
<option
value=
"sites"
>
Localités
</option>
<option
value=
"observations"
default
>
Observations
</option>
</select>
<i
class=
"fas fa-clipboard-list fa-fw"
></i>
Données
...
...
@@ -81,48 +82,74 @@
<div
v-else
>
<div
v-if=
"dataSource=='stats'"
class=
"panel-body"
>
<div
class=
"row"
style=
"text-align:center;font-size:2rem;"
>
<div
class=
"col-xs-12 col-sm-6 col-md-
3
"
>
<div
class=
"col-xs-12 col-sm-6 col-md-
4 col-lg-2
"
>
<div
class=
"statBlock"
>
<i
class=
"text-muted fas fa-map-marked-alt fa-2x"
></i><br>
<span
class=
"statCount"
>
[[sightingData.length]]
</span><br>
<span
class=
"statDesc text-muted"
>
localités
</span><br>
</div>
</div>
<div
class=
"col-xs-12 col-sm-6 col-md-
3
"
>
<div
class=
"col-xs-12 col-sm-6 col-md-
4 col-lg-2
"
>
<div
class=
"statBlock"
>
<i
class=
"text-muted fas fa-calendar-check fa-2x "
></i><br>
<span
class=
"statCount"
>
[[sessions.length]]
</span><br>
<span
class=
"statDesc text-muted"
>
sessions
</span><br>
</div>
</div>
<div
class=
"col-xs-12 col-sm-6 col-md-
3
"
>
<div
class=
"col-xs-12 col-sm-6 col-md-
4 col-lg-2
"
>
<div
class=
"statBlock"
>
<i
class=
"text-muted fas fa-calendar-check fa-2x "
></i><br>
<i
class=
"text-muted fas fa-check fa-2x "
></i><br>
<span
class=
"statCount"
>
[[taxa.filter(e => e.sp_true).length]]
</span><br>
<span
class=
"statDesc text-muted"
>
Espèces vraies
</span><br>
</div>
</div>
<div
class=
"col-xs-12 col-sm-6 col-md-4 col-lg-2"
>
<div
class=
"statBlock"
>
<i
class=
"text-muted fas fa-users fa-2x "
></i><br>
<span
class=
"statCount"
>
[[main_observers.length]]
</span><br>
<span
class=
"statDesc text-muted"
>
Observateurs principaux
</span><br>
</div>
</div>
<div
class=
"col-xs-12 col-sm-6 col-md-4 col-lg-2"
>
<div
class=
"statBlock"
>
<i
class=
"text-muted fas fa-calendar-minus fa-2x "
></i><br>
<span
class=
"statCount"
>
[[minDate]]
</span><br>
<span
class=
"statDesc text-muted"
>
Date mini
</span><br>
</div>
</div>
<div
class=
"col-xs-12 col-sm-6 col-md-
3
"
>
<div
class=
"col-xs-12 col-sm-6 col-md-
4 col-lg-2
"
>
<div
class=
"statBlock"
>
<i
class=
"text-muted fas fa-calendar-
check
fa-2x "
></i><br>
<i
class=
"text-muted fas fa-calendar-
plus
fa-2x "
></i><br>
<span
class=
"statCount"
>
[[maxDate]]
</span><br>
<span
class=
"statDesc text-muted"
>
Date max
</span><br>
</div>
</div>
</div>
{% comment %}
<pie-chart
:chart-data=
"contacts"
>
</pie-chart>
{% endcomment %}
[[contacts]]
<div
class=
"row"
>
<div
class=
"col-xs-12 col-sm-4"
>
<pie-chart
:chart-data=
"chartData"
:options=
"contactChartOptions"
></pie-chart>
</div>
<div
class=
"col-xs-12 col-sm-8"
>
<ul
class=
"list-unstyled"
>
<li
v-for=
"(t, i) in taxa"
><span
:class=
"{'text-muted':!t.sp_true}"
>
[[t.common_name]]
(
<i>
[[t.sci_name]]
</i>
)
</span>
:
<strong>
[[t.value]]
</strong>
observations
</li>
</ul>
</div>
</div>
</div>
<div
v-if=
"dataSource=='observations'"
class=
"list-group scroll-y-auto study-data-panel"
>
<session-group-item
v-for=
" (d, i) in sightingData"
:data=
"d"
:key=
"i"
>
</session-detail>
</div>
<div
v-if=
"dataSource=='sites'"
class=
"list-group scroll-y-auto study-data-panel"
>
<session-group-item
v-for=
" (d, i) in sightingData"
:data=
"d"
:key=
"i"
>
</session-detail>
</div>
</div>
<div
v-if=
"dataSource=='observations'"
class=
"list-group scroll-y-auto study-data-panel"
>
<session-group-item
v-for=
" (d, i) in sightingData"
:data=
"d"
:key=
"i"
>
</session-detail>
</div>
</div>
</div>
</div>
</div>
<script>
const
studyId
=
parseInt
(
'
{{object.pk}}
'
)
...
...
management/templates/study_list.html
0 → 100644
View file @
50991e66
{% extends 'base_fullwidth_leaflet.html' %}
{% load i18n %}
{% block title%}{{title}} | {{object.name}} - {{object.year}}{% endblock %} {% block content%}
<h1>
<i
class=
"{{ icon | safe }}"
></i>
{{ title | safe }} {{object.name}}
({{object.year}})
</h1>
<div
id=
"studyList"
class=
"row"
>
<div
class=
"col col-md-5"
>
[[studyList.length]]
</div>
<div
class=
"col col-md-7"
>
<div
class=
"list-group"
>
<div
class=
"list-group-item"
v-for=
"(s, i) in studyList"
>
<h4
class=
"list-group-item-heading"
>
<span
class=
"pull-right text-muted"
>
[[s.timestamp_update ? new Date(s.timestamp_update).toLocaleDateString() :
new Date(s.timestamp_create).toLocaleDateString()]]
</span>
<a
:href=
"`/study/${s.id_study}/detail`"
>
[[s.name]]
</a>
</h4>
<div
class=
"list-group-item-text row"
>
<div
class=
"col col-md-6"
>
<dl
class=
"dl-horizontal"
>
<dt>
Identifiant unique
</dt>
<dd><code>
[[s.uuid]]
</code></dd>
<dt>
Nom
</dt>
<dd>
[[s.name]]
</dd>
<dt>
Année
</dt>
<dd>
[[s.year]]
</dd>
<dt>
Responsable
</dt>
<dd>
[[s.project_manager?.get_full_name || '-']]
</dd>
<dt>
Type d'étude
</dt>
<dd>
[[s.type_etude]]
</dd>
<dt>
Type d'espace
</dt>
<dd>
[[s.type_espace]]
</dd>
</dl>
</div>
<div
class=
"col col-md-6"
>
<dl
class=
"dl-horizontal"
>
<dt>
Financement publique
</dt>
<dd>
<span
class=
"label"
:class=
"s.public_funding? 'label-success':'label-default'"
>
[[s.public_funding?
'Oui':'Non']]
</span>
</dd>
<dt>
Rapport rendu publique
</dt>
<dd>
<span
class=
"label"
:class=
"s.public_report? 'label-success':'label-default'"
>
[[s.public_report?
'Oui':'Non']]
</span>
</dd>
<dt>
Donnée brute publique
</dt>
<dd>
<span
class=
"label"
:class=
"s.public_raw_data? 'label-success':'label-default'"
>
[[s.public_raw_data?
'Oui':'Non']]
</span>
</dd>
<dt>
Confidentiel
</dt>
<dd>
<span
class=
"label"
:class=
"s.confidential? 'label-danger':'label-default'"
>
[[s.confidential?
'Oui':'Non']]
</span>
</dd>
</dl>
</div>
</div>
</a>
</div>
</div>
</div>
</div>
<script>
$
(
document
).
ready
(()
=>
{
const
vm
=
new
Vue
(
studyListApp
())
})
</script>
{% endblock %}
management/urls.py
View file @
50991e66
from
django.urls
import
path
from
.study.api
import
StudyDatas
,
StudySightings
from
.study.api
import
StudyDatas
Api
,
StudyListApi
,
StudySightings
Api
from
.study.views
import
StudyCreate
,
StudyDelete
,
StudyDetail
,
StudyList
,
StudyUpdate
from
.transmitter.api
import
TransmitterSearchApi
from
.transmitter.views
import
(
...
...
@@ -19,12 +19,15 @@ urlpatterns = [
path
(
"study/<int:pk>/update"
,
StudyUpdate
.
as_view
(),
name
=
"study_update"
),
path
(
"study/<int:pk>/delete"
,
StudyDelete
.
as_view
(),
name
=
"study_delete"
),
path
(
"study/list"
,
StudyList
.
as_view
(),
name
=
"study_list"
),
path
(
"api/v1/study/list"
,
StudyListApi
.
as_view
(),
name
=
"api_study_list"
),
path
(
"study/list/<int:page>"
,
StudyList
.
as_view
(),
name
=
"url_study_list"
),
path
(
"study/<int:pk>/detail"
,
StudyDetail
.
as_view
(),
name
=
"url_study_detail"
),
path
(
"api/v1/study/<int:pk>/observations"
,
StudySightings
.
as_view
(),
name
=
"api_study_sightings"
),
path
(
"api/v1/study/<int:pk>/datas"
,
StudyDatas
.
as_view
(),
name
=
"api_study_data"
),
# path(
# "api/v1/study/<int:pk>/observations",
# StudySightingsApi.as_view(),
# name="api_study_sightings",
# ),
path
(
"api/v1/study/<int:pk>/datas"
,
StudyDatasApi
.
as_view
(),
name
=
"api_study_data"
),