mirror of
https://github.com/umap-project/umap.git
synced 2025-04-28 19:42:36 +02:00
Merge 2b2098410b
into efaa765b82
This commit is contained in:
commit
119f67a3ad
17 changed files with 297 additions and 33 deletions
|
@ -91,7 +91,7 @@ class MapSettingsForm(forms.ModelForm):
|
||||||
return self.cleaned_data["center"]
|
return self.cleaned_data["center"]
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
fields = ("settings", "name", "center", "slug", "tags")
|
fields = ("settings", "name", "center", "slug", "tags", "is_template")
|
||||||
model = Map
|
model = Map
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,37 @@
|
||||||
from django.db.models import Manager
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
class PublicManager(Manager):
|
class PublicManager(models.Manager):
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return (
|
return (
|
||||||
super(PublicManager, self)
|
super(PublicManager, self)
|
||||||
.get_queryset()
|
.get_queryset()
|
||||||
.filter(share_status=self.model.PUBLIC)
|
.filter(share_status=self.model.PUBLIC)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def starred_by_staff(self):
|
||||||
|
from .models import Star, User
|
||||||
|
|
||||||
|
staff = User.objects.filter(is_staff=True)
|
||||||
|
stars = Star.objects.filter(by__in=staff).values("map")
|
||||||
|
return self.get_queryset().filter(pk__in=stars)
|
||||||
|
|
||||||
|
|
||||||
|
class PrivateQuerySet(models.QuerySet):
|
||||||
|
def for_user(self, user):
|
||||||
|
qs = self.exclude(share_status__in=[self.model.DELETED, self.model.BLOCKED])
|
||||||
|
teams = user.teams.all()
|
||||||
|
qs = (
|
||||||
|
qs.filter(owner=user)
|
||||||
|
.union(qs.filter(editors=user))
|
||||||
|
.union(qs.filter(team__in=teams))
|
||||||
|
)
|
||||||
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
class PrivateManager(models.Manager):
|
||||||
|
def get_queryset(self):
|
||||||
|
return PrivateQuerySet(self.model, using=self._db)
|
||||||
|
|
||||||
|
def for_user(self, user):
|
||||||
|
return self.get_queryset().for_user(user)
|
||||||
|
|
21
umap/migrations/0028_map_is_template.py
Normal file
21
umap/migrations/0028_map_is_template.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# Generated by Django 5.1.7 on 2025-04-17 09:44
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("umap", "0027_map_tags"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="map",
|
||||||
|
name="is_template",
|
||||||
|
field=models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text="This map is a template map.",
|
||||||
|
verbose_name="save as template",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -12,7 +12,7 @@ from django.urls import reverse
|
||||||
from django.utils.functional import classproperty
|
from django.utils.functional import classproperty
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from .managers import PublicManager
|
from .managers import PrivateManager, PublicManager
|
||||||
from .utils import _urls_for_js
|
from .utils import _urls_for_js
|
||||||
|
|
||||||
|
|
||||||
|
@ -238,9 +238,15 @@ class Map(NamedModel):
|
||||||
blank=True, null=True, verbose_name=_("settings"), default=dict
|
blank=True, null=True, verbose_name=_("settings"), default=dict
|
||||||
)
|
)
|
||||||
tags = ArrayField(models.CharField(max_length=200), blank=True, default=list)
|
tags = ArrayField(models.CharField(max_length=200), blank=True, default=list)
|
||||||
|
is_template = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
verbose_name=_("save as template"),
|
||||||
|
help_text=_("This map is a template map."),
|
||||||
|
)
|
||||||
|
|
||||||
objects = models.Manager()
|
objects = models.Manager()
|
||||||
public = PublicManager()
|
public = PublicManager()
|
||||||
|
private = PrivateManager()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def description(self):
|
def description(self):
|
||||||
|
@ -289,12 +295,15 @@ class Map(NamedModel):
|
||||||
datalayer.delete()
|
datalayer.delete()
|
||||||
return super().delete(**kwargs)
|
return super().delete(**kwargs)
|
||||||
|
|
||||||
def generate_umapjson(self, request):
|
def generate_umapjson(self, request, include_data=True):
|
||||||
umapjson = self.settings
|
umapjson = self.settings
|
||||||
umapjson["type"] = "umap"
|
umapjson["type"] = "umap"
|
||||||
|
umapjson["properties"].pop("is_template", None)
|
||||||
umapjson["uri"] = request.build_absolute_uri(self.get_absolute_url())
|
umapjson["uri"] = request.build_absolute_uri(self.get_absolute_url())
|
||||||
datalayers = []
|
datalayers = []
|
||||||
for datalayer in self.datalayers:
|
for datalayer in self.datalayers:
|
||||||
|
layer = {}
|
||||||
|
if include_data:
|
||||||
with datalayer.geojson.open("rb") as f:
|
with datalayer.geojson.open("rb") as f:
|
||||||
layer = json.loads(f.read())
|
layer = json.loads(f.read())
|
||||||
if datalayer.settings:
|
if datalayer.settings:
|
||||||
|
|
|
@ -165,7 +165,6 @@ h2.tabs a:hover {
|
||||||
min-height: var(--map-fragment-height);
|
min-height: var(--map-fragment-height);
|
||||||
}
|
}
|
||||||
.tag-list {
|
.tag-list {
|
||||||
margin-top: var(--text-margin);
|
|
||||||
margin-bottom: var(--text-margin);
|
margin-bottom: var(--text-margin);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
@ -205,6 +204,7 @@ h2.tabs a:hover {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
gap: var(--gutter);
|
gap: var(--gutter);
|
||||||
|
margin-top: var(--text-margin);
|
||||||
}
|
}
|
||||||
.card h3 {
|
.card h3 {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
margin-top: 100px;
|
margin-top: 100px;
|
||||||
width: var(--dialog-width);
|
width: var(--dialog-width);
|
||||||
max-width: 100vw;
|
max-width: 100vw;
|
||||||
max-height: 50vh;
|
max-height: 80vh;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border: 1px solid #222;
|
border: 1px solid #222;
|
||||||
background-color: var(--background-color);
|
background-color: var(--background-color);
|
||||||
|
@ -12,11 +12,14 @@
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
max-height: 90vh;
|
|
||||||
}
|
}
|
||||||
.umap-dialog ul + h4 {
|
.umap-dialog ul + h4 {
|
||||||
margin-top: var(--box-margin);
|
margin-top: var(--box-margin);
|
||||||
}
|
}
|
||||||
|
.umap-dialog .body {
|
||||||
|
max-height: 50vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
:where([data-component="no-dialog"]:not([hidden])) {
|
:where([data-component="no-dialog"]:not([hidden])) {
|
||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
|
@ -725,7 +725,7 @@ Fields.IconUrl = class extends Fields.BlurInput {
|
||||||
<button class="flat tab-url" data-ref=url>${translate('URL')}</button>
|
<button class="flat tab-url" data-ref=url>${translate('URL')}</button>
|
||||||
</div>
|
</div>
|
||||||
`)
|
`)
|
||||||
this.tabs.appendChild(root)
|
;[recent, symbols, chars, url].forEach((node) => this.tabs.appendChild(node))
|
||||||
if (Icon.RECENT.length) {
|
if (Icon.RECENT.length) {
|
||||||
recent.addEventListener('click', (event) => {
|
recent.addEventListener('click', (event) => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
|
|
@ -97,6 +97,9 @@ export default class Importer extends Utils.WithTemplate {
|
||||||
case 'banfr':
|
case 'banfr':
|
||||||
import('./importers/banfr.js').then(register)
|
import('./importers/banfr.js').then(register)
|
||||||
break
|
break
|
||||||
|
case 'templates':
|
||||||
|
import('./importers/templates.js').then(register)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
95
umap/static/umap/js/modules/importers/templates.js
Normal file
95
umap/static/umap/js/modules/importers/templates.js
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
import { DomEvent, DomUtil } from '../../../vendors/leaflet/leaflet-src.esm.js'
|
||||||
|
import { uMapAlert as Alert } from '../../components/alerts/alert.js'
|
||||||
|
import { BaseAjax, SingleMixin } from '../autocomplete.js'
|
||||||
|
import { translate } from '../i18n.js'
|
||||||
|
import * as Utils from '../utils.js'
|
||||||
|
|
||||||
|
const TEMPLATE = `
|
||||||
|
<div>
|
||||||
|
<h3>${translate('Load map template')}</h3>
|
||||||
|
<p>${translate('Use a template to initialize your map')}.</p>
|
||||||
|
<div class="formbox">
|
||||||
|
<div class="flat-tabs" data-ref="tabs">
|
||||||
|
<button type="button" class="flat" data-value="mine" data-ref="mine">${translate('My templates')}</button>
|
||||||
|
<button type="button" class="flat" data-value="staff">${translate('Staff templates')}</button>
|
||||||
|
<button type="button" class="flat" data-value="community">${translate('Community templates')}</button>
|
||||||
|
</div>
|
||||||
|
<div data-ref="body" class="body"></div>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" name="include_data" />
|
||||||
|
${translate('Include template data, if any')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
|
||||||
|
export class Importer {
|
||||||
|
constructor(umap, options = {}) {
|
||||||
|
this.umap = umap
|
||||||
|
this.name = options.name || 'Templates'
|
||||||
|
this.id = 'templates'
|
||||||
|
}
|
||||||
|
|
||||||
|
async open(importer) {
|
||||||
|
const [root, { tabs, include_data, body, mine }] =
|
||||||
|
Utils.loadTemplateWithRefs(TEMPLATE)
|
||||||
|
const uri = this.umap.urls.get('template_list')
|
||||||
|
const userIsAuth = Boolean(this.umap.properties.user?.id)
|
||||||
|
const defaultTab = userIsAuth ? 'mine' : 'staff'
|
||||||
|
mine.hidden = !userIsAuth
|
||||||
|
|
||||||
|
const loadTemplates = async (source) => {
|
||||||
|
const [data, response, error] = await this.umap.server.get(
|
||||||
|
`${uri}?source=${source}`
|
||||||
|
)
|
||||||
|
if (!error) {
|
||||||
|
body.innerHTML = ''
|
||||||
|
for (const template of data.templates) {
|
||||||
|
const item = Utils.loadTemplate(
|
||||||
|
`<dl>
|
||||||
|
<dt><label><input type="radio" value="${template.id}" name="template" />${template.name}</label></dt>
|
||||||
|
<dd>${template.description} <a href="${template.url}" target="_blank">${translate('Open')}</a></dd>
|
||||||
|
</dl>`
|
||||||
|
)
|
||||||
|
body.appendChild(item)
|
||||||
|
}
|
||||||
|
tabs.querySelectorAll('button').forEach((el) => el.classList.remove('on'))
|
||||||
|
tabs.querySelector(`[data-value="${source}"]`).classList.add('on')
|
||||||
|
} else {
|
||||||
|
console.error(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadTemplates(defaultTab)
|
||||||
|
tabs
|
||||||
|
.querySelectorAll('button')
|
||||||
|
.forEach((el) =>
|
||||||
|
el.addEventListener('click', () => loadTemplates(el.dataset.value))
|
||||||
|
)
|
||||||
|
const confirm = (form) => {
|
||||||
|
console.log(form)
|
||||||
|
if (!form.template) {
|
||||||
|
Alert.error(translate('You must select a template.'))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
let url = this.umap.urls.get('map_download', {
|
||||||
|
map_id: form.template,
|
||||||
|
})
|
||||||
|
if (!form.include_data) {
|
||||||
|
url = `${url}?include_data=0`
|
||||||
|
}
|
||||||
|
importer.url = url
|
||||||
|
importer.format = 'umap'
|
||||||
|
importer.submit()
|
||||||
|
this.umap.editPanel.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
importer.dialog
|
||||||
|
.open({
|
||||||
|
template: root,
|
||||||
|
className: `${this.id} importer dark`,
|
||||||
|
accept: translate('Use this template'),
|
||||||
|
cancel: false,
|
||||||
|
})
|
||||||
|
.then(confirm)
|
||||||
|
}
|
||||||
|
}
|
|
@ -253,6 +253,12 @@ export const SCHEMA = {
|
||||||
inheritable: true,
|
inheritable: true,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
is_template: {
|
||||||
|
type: Boolean,
|
||||||
|
impacts: ['ui'],
|
||||||
|
label: translate('This map is a template'),
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
labelDirection: {
|
labelDirection: {
|
||||||
type: String,
|
type: String,
|
||||||
impacts: ['data'],
|
impacts: ['data'],
|
||||||
|
|
|
@ -37,6 +37,7 @@ const TOP_BAR_TEMPLATE = `
|
||||||
<i class="icon icon-16 icon-save-disabled"></i>
|
<i class="icon icon-16 icon-save-disabled"></i>
|
||||||
<span hidden data-ref="saveLabel">${translate('Save')}</span>
|
<span hidden data-ref="saveLabel">${translate('Save')}</span>
|
||||||
<span hidden data-ref="saveDraftLabel">${translate('Save draft')}</span>
|
<span hidden data-ref="saveDraftLabel">${translate('Save draft')}</span>
|
||||||
|
<span hidden data-ref="saveTemplateLabel">${translate('Save template')}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>`
|
</div>`
|
||||||
|
@ -167,8 +168,11 @@ export class TopBar extends WithTemplate {
|
||||||
const syncEnabled = this._umap.getProperty('syncEnabled')
|
const syncEnabled = this._umap.getProperty('syncEnabled')
|
||||||
this.elements.peers.hidden = !syncEnabled
|
this.elements.peers.hidden = !syncEnabled
|
||||||
this.elements.view.disabled = this._umap.sync._undoManager.isDirty()
|
this.elements.view.disabled = this._umap.sync._undoManager.isDirty()
|
||||||
this.elements.saveLabel.hidden = this._umap.permissions.isDraft()
|
const isDraft = this._umap.permissions.isDraft()
|
||||||
this.elements.saveDraftLabel.hidden = !this._umap.permissions.isDraft()
|
const isTemplate = this._umap.getProperty('is_template')
|
||||||
|
this.elements.saveLabel.hidden = isDraft || isTemplate
|
||||||
|
this.elements.saveDraftLabel.hidden = !isDraft || isTemplate
|
||||||
|
this.elements.saveTemplateLabel.hidden = !isTemplate
|
||||||
this._umap.sync._undoManager.toggleState()
|
this._umap.sync._undoManager.toggleState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -768,7 +768,11 @@ export default class Umap {
|
||||||
if (!this.editEnabled) return
|
if (!this.editEnabled) return
|
||||||
if (this.properties.editMode !== 'advanced') return
|
if (this.properties.editMode !== 'advanced') return
|
||||||
const container = DomUtil.create('div')
|
const container = DomUtil.create('div')
|
||||||
const metadataFields = ['properties.name', 'properties.description']
|
const metadataFields = [
|
||||||
|
'properties.name',
|
||||||
|
'properties.description',
|
||||||
|
'properties.is_template',
|
||||||
|
]
|
||||||
|
|
||||||
DomUtil.createTitle(container, translate('Edit map details'), 'icon-caption')
|
DomUtil.createTitle(container, translate('Edit map details'), 'icon-caption')
|
||||||
const builder = new MutatingForm(this, metadataFields, {
|
const builder = new MutatingForm(this, metadataFields, {
|
||||||
|
@ -1197,6 +1201,7 @@ export default class Umap {
|
||||||
}
|
}
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('name', this.properties.name)
|
formData.append('name', this.properties.name)
|
||||||
|
formData.append('is_template', Boolean(this.properties.is_template))
|
||||||
formData.append('center', JSON.stringify(this.geometry()))
|
formData.append('center', JSON.stringify(this.geometry()))
|
||||||
formData.append('tags', this.properties.tags || [])
|
formData.append('tags', this.properties.tags || [])
|
||||||
formData.append('settings', JSON.stringify(geojson))
|
formData.append('settings', JSON.stringify(geojson))
|
||||||
|
|
|
@ -301,6 +301,15 @@
|
||||||
</form>
|
</form>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</details>
|
</details>
|
||||||
|
<details open>
|
||||||
|
<summary>With tabs</summary>
|
||||||
|
<div class="flat-tabs" data-ref="tabs">
|
||||||
|
<button class="flat on" data-ref="recent">Récents</button>
|
||||||
|
<button class="flat" data-ref="symbols">Symbole</button>
|
||||||
|
<button class="flat" data-ref="chars">Emoji & texte</button>
|
||||||
|
<button class="flat" data-ref="url">URL</button>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
</div>
|
</div>
|
||||||
<h4>Importers</h4>
|
<h4>Importers</h4>
|
||||||
<div class="umap-dialog window importers dark">
|
<div class="umap-dialog window importers dark">
|
||||||
|
|
|
@ -12,14 +12,14 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<h3>{{ map_inst.name }}</h3>
|
<h3>{% if map_inst.is_template %}<mark class="template-map">[{% trans "template" %}]</mark>{% endif %} {{ map_inst.name }}</h3>
|
||||||
{% with author=map_inst.get_author %}
|
{% with author=map_inst.get_author %}
|
||||||
{% if author %}
|
{% if author %}
|
||||||
<p>{% trans "by" %} <a href="{{ author.get_url }}">{{ author }}</a></p>
|
<p>{% trans "by" %} <a href="{{ author.get_url }}">{{ author }}</a></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
<a class="main" href="{{ map_inst.get_absolute_url }}">{% translate "See the map" %}</a>
|
<a class="main" href="{{ map_inst.get_absolute_url }}">{% if map_inst.is_template %}{% translate "See the template" %}{% else %}{% translate "See the map" %}{% endif %}</a>
|
||||||
</hgroup>
|
</hgroup>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -528,3 +528,62 @@ def test_can_find_small_usernames(client):
|
||||||
data = json.loads(response.content)["data"]
|
data = json.loads(response.content)["data"]
|
||||||
assert len(data) == 1
|
assert len(data) == 1
|
||||||
assert data[0]["label"] == "JoeJoe"
|
assert data[0]["label"] == "JoeJoe"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_templates_list(client, user, user2):
|
||||||
|
public = MapFactory(
|
||||||
|
owner=user,
|
||||||
|
name="A public template",
|
||||||
|
share_status=Map.PUBLIC,
|
||||||
|
is_template=True,
|
||||||
|
)
|
||||||
|
link_only = MapFactory(
|
||||||
|
owner=user,
|
||||||
|
name="A link-only template",
|
||||||
|
share_status=Map.OPEN,
|
||||||
|
is_template=True,
|
||||||
|
)
|
||||||
|
private = MapFactory(
|
||||||
|
owner=user,
|
||||||
|
name="A link-only template",
|
||||||
|
share_status=Map.PRIVATE,
|
||||||
|
is_template=True,
|
||||||
|
)
|
||||||
|
someone_else = MapFactory(
|
||||||
|
owner=user2,
|
||||||
|
name="A public template from someone else",
|
||||||
|
share_status=Map.PUBLIC,
|
||||||
|
is_template=True,
|
||||||
|
)
|
||||||
|
staff = UserFactory(username="Staff", is_staff=True)
|
||||||
|
Star.objects.create(by=staff, map=someone_else)
|
||||||
|
client.login(username=user.username, password="123123")
|
||||||
|
url = reverse("template_list")
|
||||||
|
|
||||||
|
# Ask for mine
|
||||||
|
response = client.get(f"{url}?source=mine")
|
||||||
|
templates = json.loads(response.content)["templates"]
|
||||||
|
ids = [t["id"] for t in templates]
|
||||||
|
assert public.pk in ids
|
||||||
|
assert link_only.pk in ids
|
||||||
|
assert private.pk in ids
|
||||||
|
assert someone_else.pk not in ids
|
||||||
|
|
||||||
|
# Ask for staff ones
|
||||||
|
response = client.get(f"{url}?source=staff")
|
||||||
|
templates = json.loads(response.content)["templates"]
|
||||||
|
ids = [t["id"] for t in templates]
|
||||||
|
assert public.pk not in ids
|
||||||
|
assert link_only.pk not in ids
|
||||||
|
assert private.pk not in ids
|
||||||
|
assert someone_else.pk in ids
|
||||||
|
|
||||||
|
# Ask for community ones
|
||||||
|
response = client.get(f"{url}?source=community")
|
||||||
|
templates = json.loads(response.content)["templates"]
|
||||||
|
ids = [t["id"] for t in templates]
|
||||||
|
assert public.pk in ids
|
||||||
|
assert link_only.pk not in ids
|
||||||
|
assert private.pk not in ids
|
||||||
|
assert someone_else.pk in ids
|
||||||
|
|
|
@ -73,6 +73,11 @@ i18n_urls = [
|
||||||
views.PictogramJSONList.as_view(),
|
views.PictogramJSONList.as_view(),
|
||||||
name="pictogram_list_json",
|
name="pictogram_list_json",
|
||||||
),
|
),
|
||||||
|
re_path(
|
||||||
|
r"^templates/json/$",
|
||||||
|
views.TemplateList.as_view(),
|
||||||
|
name="template_list",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
i18n_urls += decorated_patterns(
|
i18n_urls += decorated_patterns(
|
||||||
[can_view_map, cache_control(must_revalidate=True)],
|
[can_view_map, cache_control(must_revalidate=True)],
|
||||||
|
|
|
@ -138,9 +138,7 @@ class PublicMapsMixin(object):
|
||||||
return maps
|
return maps
|
||||||
|
|
||||||
def get_highlighted_maps(self):
|
def get_highlighted_maps(self):
|
||||||
staff = User.objects.filter(is_staff=True)
|
qs = Map.public.starred_by_staff()
|
||||||
stars = Star.objects.filter(by__in=staff).values("map")
|
|
||||||
qs = Map.public.filter(pk__in=stars)
|
|
||||||
maps = qs.order_by("-modified_at")
|
maps = qs.order_by("-modified_at")
|
||||||
return maps
|
return maps
|
||||||
|
|
||||||
|
@ -332,10 +330,10 @@ class TeamMaps(PaginatorMixin, DetailView):
|
||||||
|
|
||||||
|
|
||||||
class SearchMixin:
|
class SearchMixin:
|
||||||
def get_search_queryset(self, **kwargs):
|
def get_search_queryset(self, qs=None, **kwargs):
|
||||||
q = self.request.GET.get("q")
|
q = self.request.GET.get("q")
|
||||||
tags = [t for t in self.request.GET.getlist("tags") if t]
|
tags = [t for t in self.request.GET.getlist("tags") if t]
|
||||||
qs = Map.objects.all()
|
qs = qs or Map.public.all()
|
||||||
if q:
|
if q:
|
||||||
vector = SearchVector("name", config=settings.UMAP_SEARCH_CONFIGURATION)
|
vector = SearchVector("name", config=settings.UMAP_SEARCH_CONFIGURATION)
|
||||||
query = SearchQuery(
|
query = SearchQuery(
|
||||||
|
@ -382,14 +380,8 @@ class UserDashboard(PaginatorMixin, DetailView, SearchMixin):
|
||||||
return self.get_queryset().get(pk=self.request.user.pk)
|
return self.get_queryset().get(pk=self.request.user.pk)
|
||||||
|
|
||||||
def get_maps(self):
|
def get_maps(self):
|
||||||
qs = self.get_search_queryset() or Map.objects.all()
|
qs = Map.private.for_user(self.object)
|
||||||
qs = qs.exclude(share_status__in=[Map.DELETED, Map.BLOCKED])
|
qs = self.get_search_queryset(qs) or qs
|
||||||
teams = self.object.teams.all()
|
|
||||||
qs = (
|
|
||||||
qs.filter(owner=self.object)
|
|
||||||
.union(qs.filter(editors=self.object))
|
|
||||||
.union(qs.filter(team__in=teams))
|
|
||||||
)
|
|
||||||
return qs.order_by("-modified_at")
|
return qs.order_by("-modified_at")
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
@ -408,9 +400,9 @@ class UserDownload(DetailView, SearchMixin):
|
||||||
return self.get_queryset().get(pk=self.request.user.pk)
|
return self.get_queryset().get(pk=self.request.user.pk)
|
||||||
|
|
||||||
def get_maps(self):
|
def get_maps(self):
|
||||||
qs = Map.objects.filter(id__in=self.request.GET.getlist("map_id"))
|
qs = Map.private.filter(id__in=self.request.GET.getlist("map_id"))
|
||||||
qs = qs.filter(owner=self.object).union(qs.filter(editors=self.object))
|
qsu = qs.for_user(self.object)
|
||||||
return qs.order_by("-modified_at")
|
return qsu.order_by("-modified_at")
|
||||||
|
|
||||||
def render_to_response(self, context, *args, **kwargs):
|
def render_to_response(self, context, *args, **kwargs):
|
||||||
zip_buffer = io.BytesIO()
|
zip_buffer = io.BytesIO()
|
||||||
|
@ -802,7 +794,10 @@ class MapDownload(DetailView):
|
||||||
return reverse("map_download", args=(self.object.pk,))
|
return reverse("map_download", args=(self.object.pk,))
|
||||||
|
|
||||||
def render_to_response(self, context, *args, **kwargs):
|
def render_to_response(self, context, *args, **kwargs):
|
||||||
umapjson = self.object.generate_umapjson(self.request)
|
include_data = self.request.GET.get("include_data") != "0"
|
||||||
|
umapjson = self.object.generate_umapjson(
|
||||||
|
self.request, include_data=include_data
|
||||||
|
)
|
||||||
response = simple_json_response(**umapjson)
|
response = simple_json_response(**umapjson)
|
||||||
response["Content-Disposition"] = (
|
response["Content-Disposition"] = (
|
||||||
f'attachment; filename="umap_backup_{self.object.slug}.umap"'
|
f'attachment; filename="umap_backup_{self.object.slug}.umap"'
|
||||||
|
@ -1456,3 +1451,26 @@ class LoginPopupEnd(TemplateView):
|
||||||
if backend in settings.DEPRECATED_AUTHENTICATION_BACKENDS:
|
if backend in settings.DEPRECATED_AUTHENTICATION_BACKENDS:
|
||||||
return HttpResponseRedirect(reverse("user_profile"))
|
return HttpResponseRedirect(reverse("user_profile"))
|
||||||
return super().get(*args, **kwargs)
|
return super().get(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateList(ListView):
|
||||||
|
model = Map
|
||||||
|
|
||||||
|
def render_to_response(self, context, **response_kwargs):
|
||||||
|
source = self.request.GET.get("source")
|
||||||
|
if source == "mine":
|
||||||
|
qs = Map.private.filter(is_template=True).for_user(self.request.user)
|
||||||
|
elif source == "community":
|
||||||
|
qs = Map.public.filter(is_template=True)
|
||||||
|
elif source == "staff":
|
||||||
|
qs = Map.public.starred_by_staff().filter(is_template=True)
|
||||||
|
templates = [
|
||||||
|
{
|
||||||
|
"id": m.id,
|
||||||
|
"name": m.name,
|
||||||
|
"description": m.description,
|
||||||
|
"url": m.get_absolute_url(),
|
||||||
|
}
|
||||||
|
for m in qs.order_by("-modified_at")
|
||||||
|
]
|
||||||
|
return simple_json_response(templates=templates)
|
||||||
|
|
Loading…
Reference in a new issue