Skip to content

Commit ac03ad0

Browse files
authored
Merge pull request #1633 from pierotofy/projshare
Share Project Links
2 parents 709e998 + 896d4ac commit ac03ad0

17 files changed

+351
-140
lines changed

app/api/potree.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from rest_framework.response import Response
44
from rest_framework import exceptions
55

6+
67
class Scene(TaskNestedView):
78
def get(self, request, pk=None, project_pk=None):
89
"""
@@ -17,7 +18,7 @@ def post(self, request, pk=None, project_pk=None):
1718
Store potree scene information (except camera view)
1819
"""
1920
task = self.get_and_check_task(request, pk)
20-
if (not task.public) or (task.public and not task.public_edit):
21+
if task.check_public_edit():
2122
get_and_check_project(request, project_pk, perms=("change_project", ))
2223
scene = request.data
2324

@@ -38,7 +39,7 @@ def post(self, request, pk=None, project_pk=None):
3839
Store camera view information
3940
"""
4041
task = self.get_and_check_task(request, pk)
41-
if (not task.public) or (task.public and not task.public_edit):
42+
if task.check_public_edit():
4243
get_and_check_project(request, project_pk, perms=("change_project", ))
4344

4445
view = request.data

app/api/tasks.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ def retrieve(self, request, pk=None, project_pk=None):
213213
except (ObjectDoesNotExist, ValidationError):
214214
raise exceptions.NotFound()
215215

216-
if not task.public:
216+
if not (task.public or task.project.public):
217217
get_and_check_project(request, task.project.id)
218218

219219
serializer = TaskSerializer(task)
@@ -372,7 +372,7 @@ def get_and_check_task(self, request, pk, annotate={}):
372372
raise exceptions.NotFound()
373373

374374
# Check for permissions, unless the task is public
375-
if not task.public:
375+
if not (task.public or task.project.public):
376376
get_and_check_project(request, task.project.id)
377377

378378
return task
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Generated by Django 2.2.27 on 2025-03-21 16:30
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('app', '0041_auto_20250311_2007'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='project',
15+
name='public',
16+
field=models.BooleanField(default=False, help_text='A flag indicating whether this project is available to the public', verbose_name='Public'),
17+
),
18+
migrations.AddField(
19+
model_name='project',
20+
name='public_edit',
21+
field=models.BooleanField(default=False, help_text='A flag indicating whether this public project can be edited', verbose_name='Public Edit'),
22+
),
23+
migrations.AddField(
24+
model_name='project',
25+
name='public_id',
26+
field=models.UUIDField(blank=True, db_index=True, default=None, help_text='Public identifier of the project', null=True, unique=True, verbose_name='Public Id'),
27+
),
28+
]

app/models/project.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
import uuid
23

34
from django.conf import settings
45
from django.db import models
@@ -26,7 +27,11 @@ class Project(models.Model):
2627
created_at = models.DateTimeField(default=timezone.now, help_text=_("Creation date"), verbose_name=_("Created at"))
2728
deleting = models.BooleanField(db_index=True, default=False, help_text=_("Whether this project has been marked for deletion. Projects that have running tasks need to wait for tasks to be properly cleaned up before they can be deleted."), verbose_name=_("Deleting"))
2829
tags = models.TextField(db_index=True, default="", blank=True, help_text=_("Project tags"), verbose_name=_("Tags"))
30+
public = models.BooleanField(default=False, help_text=_("A flag indicating whether this project is available to the public"), verbose_name=_("Public"))
31+
public_edit = models.BooleanField(default=False, help_text=_("A flag indicating whether this public project can be edited"), verbose_name=_("Public Edit"))
32+
public_id = models.UUIDField(db_index=True, default=None, unique=True, blank=True, null=True, help_text=_("Public identifier of the project"), verbose_name=_("Public Id"))
2933

34+
3035
def delete(self, *args):
3136
# No tasks?
3237
if self.task_set.count() == 0:
@@ -56,7 +61,15 @@ def get_map_items(self):
5661
status=status_codes.COMPLETED
5762
).filter(Q(orthophoto_extent__isnull=False) | Q(dsm_extent__isnull=False) | Q(dtm_extent__isnull=False))
5863
.only('id', 'project_id')]
59-
64+
65+
def get_public_info(self):
66+
return {
67+
'id': self.id,
68+
'public': self.public,
69+
'public_id': str(self.public_id) if self.public_id is not None else None,
70+
'public_edit': self.public_edit
71+
}
72+
6073
def duplicate(self, new_owner=None):
6174
try:
6275
with transaction.atomic():
@@ -84,7 +97,12 @@ def duplicate(self, new_owner=None):
8497

8598
return False
8699

100+
def save(self, *args, **kwargs):
101+
# Assign a public ID if missing and public = True
102+
if self.public and self.public_id is None:
103+
self.public_id = uuid.uuid4()
87104

105+
super(Project, self).save(*args, **kwargs)
88106

89107
class Meta:
90108
verbose_name = _("Project")

app/models/task.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1173,6 +1173,16 @@ def compact(self):
11731173
self.compacted = True
11741174
self.update_size(commit=True)
11751175

1176+
def check_public_edit(self):
1177+
"""
1178+
Returns whether we need to check change permissions on this task
1179+
during an API call that needs to make edits
1180+
"""
1181+
public = self.public or self.project.public
1182+
public_edit = self.public_edit or self.project.public_edit
1183+
1184+
return (not public) or (public and not public_edit)
1185+
11761186
def set_failure(self, error_message):
11771187
logger.error("FAILURE FOR {}: {}".format(self, error_message))
11781188
self.last_error = error_message

app/static/app/js/MapView.jsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ class MapView extends React.Component {
1313
public: false,
1414
publicEdit: false,
1515
shareButtons: true,
16-
permissions: ["view"]
16+
permissions: ["view"],
17+
project: null
1718
};
1819

1920
static propTypes = {
@@ -23,7 +24,8 @@ class MapView extends React.Component {
2324
public: PropTypes.bool,
2425
publicEdit: PropTypes.bool,
2526
shareButtons: PropTypes.bool,
26-
permissions: PropTypes.array
27+
permissions: PropTypes.array,
28+
project: PropTypes.object
2729
};
2830

2931
constructor(props){
@@ -171,6 +173,7 @@ class MapView extends React.Component {
171173
shareButtons={this.props.shareButtons}
172174
permissions={this.props.permissions}
173175
thermal={isThermal}
176+
project={this.props.project}
174177
/>
175178
</div>
176179
</div>);

app/static/app/js/components/Map.jsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ class Map extends React.Component {
5050
publicEdit: PropTypes.bool,
5151
shareButtons: PropTypes.bool,
5252
permissions: PropTypes.array,
53-
thermal: PropTypes.bool
53+
thermal: PropTypes.bool,
54+
project: PropTypes.object
5455
};
5556

5657
constructor(props) {
@@ -837,15 +838,18 @@ _('Example:'),
837838
/>
838839

839840
<div className="actionButtons">
841+
840842
{this.state.pluginActionButtons.map((button, i) => <div key={i}>{button}</div>)}
841-
{(this.props.shareButtons && !this.props.public && this.state.singleTask !== null) ?
843+
{((this.state.singleTask || this.props.project) && this.props.shareButtons && !this.props.public) ?
842844
<ShareButton
843845
ref={(ref) => { this.shareButton = ref; }}
844-
task={this.state.singleTask}
846+
task={this.state.singleTask}
847+
project={this.props.project}
845848
linksTarget="map"
846849
queryParams={{t: this.props.mapType}}
847850
/>
848851
: ""}
852+
849853
<SwitchModeButton
850854
task={this.state.singleTask}
851855
type="mapToModel"

app/static/app/js/components/ShareButton.jsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ import { _ } from '../classes/gettext';
77
class ShareButton extends React.Component {
88
static defaultProps = {
99
task: null,
10+
project: null,
1011
popupPlacement: 'top'
1112
};
1213
static propTypes = {
13-
task: PropTypes.object.isRequired,
14+
task: PropTypes.object,
15+
project: PropTypes.object,
1416
linksTarget: PropTypes.oneOf(['map', '3d']).isRequired,
1517
popupPlacement: PropTypes.string,
1618
queryParams: PropTypes.object
@@ -21,11 +23,13 @@ class ShareButton extends React.Component {
2123

2224
this.state = {
2325
showPopup: false,
24-
task: props.task
26+
task: props.task,
27+
project: props.project
2528
};
2629

2730
this.handleClick = this.handleClick.bind(this);
2831
this.handleTaskChanged = this.handleTaskChanged.bind(this);
32+
this.handleProjectChanged = this.handleProjectChanged.bind(this);
2933
}
3034

3135
handleClick(e){
@@ -40,14 +44,23 @@ class ShareButton extends React.Component {
4044
this.setState({task});
4145
}
4246

47+
handleProjectChanged(project){
48+
this.setState({project});
49+
}
50+
4351
render() {
4452
const popup = <SharePopup
4553
task={this.state.task}
4654
taskChanged={this.handleTaskChanged}
55+
project={this.state.project}
56+
projectChanged={this.handleProjectChanged}
57+
4758
placement={this.props.popupPlacement}
4859
linksTarget={this.props.linksTarget}
4960
queryParams={this.props.queryParams}
5061
/>;
62+
63+
const isPublic = this.state.task ? this.state.task.public : this.state.project.public;
5164

5265
return (
5366
<div className="shareButton" onClick={e => { e.stopPropagation(); }}>
@@ -56,7 +69,7 @@ class ShareButton extends React.Component {
5669
<button
5770
type="button"
5871
onClick={this.handleClick}
59-
className={"shareButton btn btn-sm " + (this.state.task.public ? "btn-primary" : "btn-secondary")}>
72+
className={"shareButton btn btn-sm " + (isPublic ? "btn-primary" : "btn-secondary")}>
6073
<i className="fa fa-share-alt"></i> {_("Share")}
6174
</button>
6275
{this.props.popupPlacement === 'bottom' && this.state.showPopup ?

0 commit comments

Comments
 (0)