Compare commits

..

2 commits

Author SHA1 Message Date
Yohan Boniface
a27fb5ab5f
Merge aa03664e2b into 23b82975f5 2025-04-17 17:03:55 +02:00
Yohan Boniface
aa03664e2b wip: first naive version of map templates
Co-authored-by: David Larlet <david@larlet.fr>
2025-04-17 17:03:19 +02:00
8 changed files with 54 additions and 309 deletions

View file

@ -9,13 +9,6 @@ class PublicManager(models.Manager):
.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): class PrivateQuerySet(models.QuerySet):
def for_user(self, user): def for_user(self, user):

View file

@ -298,7 +298,7 @@ class Map(NamedModel):
def generate_umapjson(self, request, include_data=True): 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["properties"]["is_template"] = False
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:

View file

@ -1,6 +1,6 @@
.umap-main-edit-toolbox [type=button] { .umap-main-edit-toolbox [type=button] {
color: #fff; color: #fff;
font-size: 0.8rem; font-size: 1em;
background-color: var(--color-darkGray); background-color: var(--color-darkGray);
width: auto; width: auto;
margin-bottom: 0; margin-bottom: 0;

View file

@ -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: 80vh; max-height: 50vh;
padding: 20px; padding: 20px;
border: 1px solid #222; border: 1px solid #222;
background-color: var(--background-color); background-color: var(--background-color);
@ -12,14 +12,11 @@
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;

View file

@ -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>
`) `)
;[recent, symbols, chars, url].forEach((node) => this.tabs.appendChild(node)) this.tabs.appendChild(root)
if (Icon.RECENT.length) { if (Icon.RECENT.length) {
recent.addEventListener('click', (event) => { recent.addEventListener('click', (event) => {
event.stopPropagation() event.stopPropagation()

View file

@ -4,22 +4,26 @@ import { BaseAjax, SingleMixin } from '../autocomplete.js'
import { translate } from '../i18n.js' import { translate } from '../i18n.js'
import * as Utils from '../utils.js' import * as Utils from '../utils.js'
const BOUNDARY_TYPES = {
admin_6: 'département',
admin_7: 'pays (loi Voynet)',
admin_8: 'commune',
admin_9: 'quartier, hameau, arrondissement',
political: 'canton',
local_authority: 'EPCI',
}
const TEMPLATE = ` const TEMPLATE = `
<div> <h3>${translate('Load template')}</h3>
<h3>${translate('Load map template')}</h3> <p>${translate('GeoDataMine: thematic data from OpenStreetMap')}.</p>
<p>${translate('Use a template to initialize your map')}.</p> <div class="formbox">
<div class="formbox"> <select name="theme">
<div class="flat-tabs" data-ref="tabs"> <option value="">${translate('Choose a template')}</option>
<button type="button" class="flat" data-value="mine" data-ref="mine">${translate('My templates')}</button> </select>
<button type="button" class="flat" data-value="staff">${translate('Staff templates')}</button> <label>
<button type="button" class="flat" data-value="community">${translate('Community templates')}</button> <input type="checkbox" name="include_data" />
</div> ${translate('Include template data, if any')}
<div data-ref="body" class="body"></div> </label>
<label>
<input type="checkbox" name="include_data" />
${translate('Include template data, if any')}
</label>
</div>
</div> </div>
` `
@ -31,50 +35,28 @@ export class Importer {
} }
async open(importer) { async open(importer) {
const [root, { tabs, include_data, body, mine }] = const container = DomUtil.create('div')
Utils.loadTemplateWithRefs(TEMPLATE) container.innerHTML = TEMPLATE
const select = container.querySelector('select')
const uri = this.umap.urls.get('template_list') const uri = this.umap.urls.get('template_list')
const userIsAuth = Boolean(this.umap.properties.user?.id) const [data, response, error] = await this.umap.server.get(uri)
const defaultTab = userIsAuth ? 'mine' : 'staff' if (!error) {
mine.hidden = !userIsAuth for (const template of data.templates) {
DomUtil.element({
const loadTemplates = async (source) => { tagName: 'option',
const [data, response, error] = await this.umap.server.get( value: template.id,
`${uri}?source=${source}` textContent: template.name,
) parent: select,
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}</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)
} }
} else {
console.error(response)
} }
loadTemplates(defaultTab)
tabs
.querySelectorAll('button')
.forEach((el) =>
el.addEventListener('click', () => loadTemplates(el.dataset.value))
)
const confirm = (form) => { 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', { let url = this.umap.urls.get('map_download', {
map_id: form.template, map_id: select.options[select.selectedIndex].value,
}) })
if (!form.include_data) { if (!container.querySelector('[name=include_data]').checked) {
url = `${url}?include_data=0` url = `${url}?include_data=0`
} }
importer.url = url importer.url = url
@ -85,7 +67,7 @@ export class Importer {
importer.dialog importer.dialog
.open({ .open({
template: root, template: container,
className: `${this.id} importer dark`, className: `${this.id} importer dark`,
accept: translate('Use this template'), accept: translate('Use this template'),
cancel: false, cancel: false,

File diff suppressed because one or more lines are too long

View file

@ -138,7 +138,9 @@ class PublicMapsMixin(object):
return maps return maps
def get_highlighted_maps(self): def get_highlighted_maps(self):
qs = Map.public.starred_by_staff() staff = User.objects.filter(is_staff=True)
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
@ -400,9 +402,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.private.filter(id__in=self.request.GET.getlist("map_id")) qs = Map.private.for_user(self.object)
qsu = qs.for_user(self.object) qs = qs.filter(id__in=self.request.GET.getlist("map_id"))
return qsu.order_by("-modified_at") return qs.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()
@ -1457,14 +1459,9 @@ class TemplateList(ListView):
model = Map model = Map
def render_to_response(self, context, **response_kwargs): def render_to_response(self, context, **response_kwargs):
source = self.request.GET.get("source") if self.request.user.is_authenticated:
if source == "mine":
qs = Map.private.filter(is_template=True).for_user(self.request.user) qs = Map.private.filter(is_template=True).for_user(self.request.user)
elif source == "community": else:
qs = Map.public.filter(is_template=True) qs = Map.public.filter(is_template=True)
elif source == "staff": templates = [{"id": m.id, "name": m.name} for m in qs]
qs = Map.public.starred_by_staff().filter(is_template=True)
templates = [
{"id": m.id, "name": m.name, "description": m.description} for m in qs
]
return simple_json_response(templates=templates) return simple_json_response(templates=templates)