kopia lustrzana https://github.com/OpenDroneMap/WebODM
Dropzone initialized proof of concept, task api (still need to test)
rodzic
c2e35d30d4
commit
d25556ceae
|
@ -1,8 +1,6 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from guardian.admin import GuardedModelAdmin
|
from guardian.admin import GuardedModelAdmin
|
||||||
from .models import Project
|
from .models import Project, Task
|
||||||
|
|
||||||
class ProjectAdmin(GuardedModelAdmin):
|
admin.site.register(Project, GuardedModelAdmin)
|
||||||
pass
|
admin.site.register(Task, GuardedModelAdmin)
|
||||||
|
|
||||||
admin.site.register(Project, ProjectAdmin)
|
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from rest_framework import serializers, viewsets, filters
|
from rest_framework import serializers, viewsets, filters
|
||||||
from app import models, permissions
|
from rest_framework.response import Response
|
||||||
from guardian.shortcuts import get_objects_for_user
|
from rest_framework.decorators import detail_route
|
||||||
|
from app import models
|
||||||
|
from .tasks import TaskIDsSerializer, TaskSerializer
|
||||||
|
|
||||||
class ProjectSerializer(serializers.HyperlinkedModelSerializer):
|
class ProjectSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
id = serializers.ReadOnlyField()
|
|
||||||
owner = serializers.PrimaryKeyRelatedField(queryset=User.objects.all())
|
owner = serializers.PrimaryKeyRelatedField(queryset=User.objects.all())
|
||||||
|
tasks = TaskIDsSerializer(many=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Project
|
model = models.Project
|
||||||
|
@ -14,8 +15,20 @@ class ProjectSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
|
|
||||||
class ProjectViewSet(viewsets.ModelViewSet):
|
class ProjectViewSet(viewsets.ModelViewSet):
|
||||||
"""
|
"""
|
||||||
Projects the current user has access to.
|
Projects the current user has access to. Projects are the building blocks
|
||||||
|
of processing. Each project can have zero or more tasks associated with it.
|
||||||
|
Users can fine tune the permissions on projects, including whether users/groups have
|
||||||
|
access to view, add, change or delete them.<br/><br/>
|
||||||
|
- /api/projects/<projectId>/tasks : list all tasks belonging to a project<br/>
|
||||||
|
- /api/projects/<projectId>/tasks/<taskId> : get task details
|
||||||
|
|
||||||
"""
|
"""
|
||||||
filter_fields = ('id', 'owner', 'name')
|
filter_fields = ('id', 'owner', 'name')
|
||||||
serializer_class = ProjectSerializer
|
serializer_class = ProjectSerializer
|
||||||
queryset = models.Project.objects.all()
|
queryset = models.Project.objects.all()
|
||||||
|
|
||||||
|
@detail_route(methods=['get'])
|
||||||
|
def tasks(self, request, pk=None):
|
||||||
|
tasks = self.get_object().tasks()
|
||||||
|
serializer = TaskSerializer(tasks, many=True)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from rest_framework import serializers, viewsets, filters
|
||||||
|
from app import models
|
||||||
|
from nodeodm.models import ProcessingNode
|
||||||
|
|
||||||
|
class TaskIDsSerializer(serializers.BaseSerializer):
|
||||||
|
def to_representation(self, obj):
|
||||||
|
return obj.id
|
||||||
|
|
||||||
|
class TaskSerializer(serializers.ModelSerializer):
|
||||||
|
project = serializers.PrimaryKeyRelatedField(queryset=models.Project.objects.all())
|
||||||
|
processing_node = serializers.PrimaryKeyRelatedField(queryset=ProcessingNode.objects.all())
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.Task
|
||||||
|
|
|
@ -14,8 +14,8 @@ def boot():
|
||||||
if created:
|
if created:
|
||||||
logger.info("Created default group")
|
logger.info("Created default group")
|
||||||
|
|
||||||
# Add default permissions (view_project, change_project, delete_task, etc.)
|
# Add default permissions (view_project, change_project, delete_project, etc.)
|
||||||
for permission in ('_project', '_task'):
|
for permission in ('_project'):
|
||||||
default_group.permissions.add(
|
default_group.permissions.add(
|
||||||
*list(Permission.objects.filter(codename__endswith=permission))
|
*list(Permission.objects.filter(codename__endswith=permission))
|
||||||
)
|
)
|
||||||
|
|
|
@ -25,6 +25,9 @@ class Project(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
def tasks(self):
|
||||||
|
return Task.objects.filter(project=self);
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
permissions = (
|
permissions = (
|
||||||
('view_project', 'Can view project'),
|
('view_project', 'Can view project'),
|
||||||
|
@ -60,15 +63,15 @@ class Task(models.Model):
|
||||||
(50, 'CANCELED')
|
(50, 'CANCELED')
|
||||||
)
|
)
|
||||||
|
|
||||||
uuid = models.CharField(max_length=255, primary_key=True, help_text="Unique identifier of the task (as returned by OpenDroneMap's REST API)")
|
uuid = models.CharField(max_length=255, null=True, blank=True, unique=True, help_text="Unique identifier of the task (as returned by OpenDroneMap's REST API)")
|
||||||
project = models.ForeignKey(Project, on_delete=models.CASCADE, help_text="Project that this task belongs to")
|
project = models.ForeignKey(Project, on_delete=models.CASCADE, help_text="Project that this task belongs to")
|
||||||
name = models.CharField(max_length=255, help_text="A label for the task")
|
name = models.CharField(max_length=255, null=True, blank=True, help_text="A label for the task")
|
||||||
processing_time = models.IntegerField(default=-1, help_text="Number of milliseconds that elapsed since the beginning of this task (-1 indicates that no information is available)")
|
processing_time = models.IntegerField(default=-1, help_text="Number of milliseconds that elapsed since the beginning of this task (-1 indicates that no information is available)")
|
||||||
processing_node = models.ForeignKey(ProcessingNode, null=True, help_text="Processing node assigned to this task (or null if this task has not been associated yet)")
|
processing_node = models.ForeignKey(ProcessingNode, null=True, blank=True, help_text="Processing node assigned to this task (or null if this task has not been associated yet)")
|
||||||
status = models.IntegerField(choices=STATUS_CODES, null=True, help_text="Current status of the task")
|
status = models.IntegerField(choices=STATUS_CODES, null=True, blank=True, help_text="Current status of the task")
|
||||||
options = fields.JSONField(default=dict(), help_text="Options that are being used to process this task")
|
options = fields.JSONField(default=dict(), blank=True, help_text="Options that are being used to process this task")
|
||||||
console_output = models.TextField(null=True, help_text="Console output of the OpenDroneMap's process")
|
console_output = models.TextField(null=True, blank=True, help_text="Console output of the OpenDroneMap's process")
|
||||||
ground_control_points = models.FileField(null=True, upload_to=gcp_directory_path, help_text="Optional Ground Control Points file to use for processing")
|
ground_control_points = models.FileField(null=True, blank=True, upload_to=gcp_directory_path, help_text="Optional Ground Control Points file to use for processing")
|
||||||
# georeferenced_model
|
# georeferenced_model
|
||||||
# orthophoto
|
# orthophoto
|
||||||
# textured_model
|
# textured_model
|
||||||
|
|
|
@ -34,7 +34,7 @@ class ProjectList extends React.Component {
|
||||||
error: `Could not load projects list: ${textStatus}`,
|
error: `Could not load projects list: ${textStatus}`,
|
||||||
loading: false
|
loading: false
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount(){
|
componentWillUnmount(){
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ProjectListItemPanel from './ProjectListItemPanel';
|
import ProjectListItemPanel from './ProjectListItemPanel';
|
||||||
|
import Dropzone from '../vendor/dropzone';
|
||||||
|
import $ from 'jquery';
|
||||||
|
|
||||||
class ProjectListItem extends React.Component {
|
class ProjectListItem extends React.Component {
|
||||||
constructor(props){
|
constructor(props){
|
||||||
|
@ -8,10 +10,27 @@ class ProjectListItem extends React.Component {
|
||||||
this.state = {
|
this.state = {
|
||||||
showPanel: false
|
showPanel: false
|
||||||
};
|
};
|
||||||
|
this.dropzoneInitialized = false;
|
||||||
|
|
||||||
this.togglePanel = this.togglePanel.bind(this);
|
this.togglePanel = this.togglePanel.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initializeDropzone(domNode){
|
||||||
|
if (domNode != null && !this.dropzoneInitialized){
|
||||||
|
Dropzone.autoDiscover = false;
|
||||||
|
|
||||||
|
let dropzone = new Dropzone(domNode, {
|
||||||
|
url : '/api/upload'
|
||||||
|
});
|
||||||
|
|
||||||
|
dropzone.on("complete", function(file) {
|
||||||
|
console.log(file);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.dropzoneInitialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
togglePanel(){
|
togglePanel(){
|
||||||
this.setState({
|
this.setState({
|
||||||
showPanel: !this.state.showPanel
|
showPanel: !this.state.showPanel
|
||||||
|
@ -43,6 +62,12 @@ class ProjectListItem extends React.Component {
|
||||||
{this.props.data.name}
|
{this.props.data.name}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<div className="dropzone" ref={domNode => this.initializeDropzone(domNode)}>
|
||||||
|
<div className="dz-default dz-message text-center">
|
||||||
|
<i className="fa fa-cloud-upload fa-4x"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{this.state.showPanel ? <ProjectListItemPanel /> : ""}
|
{this.state.showPanel ? <ProjectListItemPanel /> : ""}
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
|
|
Plik diff jest za duży
Load Diff
|
@ -21,22 +21,22 @@
|
||||||
<button class="btn btn-primary" onclick="location.href='{% url "admin:nodeodm_processingnode_add" %}';"><i class="fa fa-plus-circle"></i> {{ add_processing_node }}</button>
|
<button class="btn btn-primary" onclick="location.href='{% url "admin:nodeodm_processingnode_add" %}';"><i class="fa fa-plus-circle"></i> {{ add_processing_node }}</button>
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
||||||
{% trans 'Upload Images' as upload_images %}
|
|
||||||
<p>
|
|
||||||
{% blocktrans %} To create a new project, press the "{{ upload_images }}" button. {% endblocktrans %}
|
|
||||||
</p>
|
|
||||||
<button class="btn btn-primary"><i class="glyphicon glyphicon-upload"></i> {{ upload_images }}</button>
|
|
||||||
|
|
||||||
<p>
|
{% if no_tasks %}
|
||||||
<ul>
|
{% trans 'Upload Images' as upload_images %}
|
||||||
<li>{% trans 'Need at least 5 images' %}</li>
|
<p>
|
||||||
<li>{% trans 'Images must overlap by 60% or more' %}</li>
|
{% blocktrans %} To create a map, press the "{{ upload_images }}" button. {% endblocktrans %}
|
||||||
</ul>
|
</p>
|
||||||
</p>
|
<p>
|
||||||
{% endif %}
|
<ul>
|
||||||
<!-- <h4>Projects</h4> -->
|
<li>{% trans 'You need at least 5 images' %}</li>
|
||||||
|
<li>{% trans 'Images must overlap by 60% or more' %}</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div id="dashboard-app"></div>
|
<div id="dashboard-app"></div>
|
||||||
{% render_bundle 'main' %}
|
{% render_bundle 'main' %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from django.shortcuts import render, redirect, get_object_or_404
|
from django.shortcuts import render, redirect, get_object_or_404
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from nodeodm.models import ProcessingNode
|
from nodeodm.models import ProcessingNode
|
||||||
from .models import Project
|
from .models import Project, Task
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
@ -13,14 +13,15 @@ def index(request):
|
||||||
@login_required
|
@login_required
|
||||||
def dashboard(request):
|
def dashboard(request):
|
||||||
no_processingnodes = ProcessingNode.objects.count() == 0
|
no_processingnodes = ProcessingNode.objects.count() == 0
|
||||||
|
no_tasks = Task.objects.filter(project__owner=request.user).count() == 0
|
||||||
|
|
||||||
# Create first project automatically
|
# Create first project automatically
|
||||||
if Project.objects.filter(owner=request.user).count() == 0:
|
if Project.objects.filter(owner=request.user).count() == 0:
|
||||||
proj = Project(owner=request.user, name=_("First Project"))
|
Project.objects.create(owner=request.user, name=_("First Project"))
|
||||||
proj.save()
|
|
||||||
|
|
||||||
return render(request, 'app/dashboard.html', {'title': 'Dashboard',
|
return render(request, 'app/dashboard.html', {'title': 'Dashboard',
|
||||||
'no_processingnodes': no_processingnodes})
|
'no_processingnodes': no_processingnodes,
|
||||||
|
'no_tasks': no_tasks})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def processing_node(request, processing_node_id):
|
def processing_node(request, processing_node_id):
|
||||||
|
|
Ładowanie…
Reference in New Issue