Merge pull request #683 from umap-project/fav
Allow to star maps and retrieve starred maps
25
umap/migrations/0009_star.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# Generated by Django 4.1.7 on 2023-05-05 18:02
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('umap', '0008_alter_map_settings'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Star',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('at', models.DateTimeField(auto_now=True)),
|
||||||
|
('by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='stars', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('map', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='umap.map')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -349,3 +349,9 @@ class DataLayer(NamedModel):
|
||||||
self.geojson.storage.delete(path)
|
self.geojson.storage.delete(path)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Star(models.Model):
|
||||||
|
at = models.DateTimeField(auto_now=True)
|
||||||
|
map = models.ForeignKey(Map, on_delete=models.CASCADE)
|
||||||
|
by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="stars", on_delete=models.CASCADE)
|
||||||
|
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
@ -13,7 +13,7 @@
|
||||||
height="200"
|
height="200"
|
||||||
id="svg2"
|
id="svg2"
|
||||||
version="1.1"
|
version="1.1"
|
||||||
inkscape:version="0.92.2 2405546, 2018-03-11"
|
inkscape:version="0.92.4 5da689c313, 2019-01-14"
|
||||||
sodipodi:docname="24-white.svg"
|
sodipodi:docname="24-white.svg"
|
||||||
inkscape:export-filename="/home/ybon/Code/py/umap/umap/static/umap/img/24-white.png"
|
inkscape:export-filename="/home/ybon/Code/py/umap/umap/static/umap/img/24-white.png"
|
||||||
inkscape:export-xdpi="96"
|
inkscape:export-xdpi="96"
|
||||||
|
@ -27,9 +27,9 @@
|
||||||
borderopacity="1.0"
|
borderopacity="1.0"
|
||||||
inkscape:pageopacity="0.0"
|
inkscape:pageopacity="0.0"
|
||||||
inkscape:pageshadow="2"
|
inkscape:pageshadow="2"
|
||||||
inkscape:zoom="22.4"
|
inkscape:zoom="16"
|
||||||
inkscape:cx="124.98783"
|
inkscape:cx="119.65216"
|
||||||
inkscape:cy="45.00337"
|
inkscape:cy="34.240239"
|
||||||
inkscape:document-units="px"
|
inkscape:document-units="px"
|
||||||
inkscape:current-layer="layer1"
|
inkscape:current-layer="layer1"
|
||||||
showgrid="true"
|
showgrid="true"
|
||||||
|
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 16 KiB |
|
@ -2,22 +2,22 @@
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
<svg
|
<svg
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
width="200"
|
width="200"
|
||||||
height="200"
|
height="200"
|
||||||
id="svg2"
|
id="svg2"
|
||||||
version="1.1"
|
version="1.1"
|
||||||
inkscape:version="0.92.4 5da689c313, 2019-01-14"
|
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||||
sodipodi:docname="24.svg"
|
sodipodi:docname="24.svg"
|
||||||
inkscape:export-filename="/home/ybon/Code/js/Leaflet.Storage/src/img/24.png"
|
inkscape:export-filename="/home/ybon/Code/js/Leaflet.Storage/src/img/24.png"
|
||||||
inkscape:export-xdpi="89.996864"
|
inkscape:export-xdpi="89.996864"
|
||||||
inkscape:export-ydpi="89.996864">
|
inkscape:export-ydpi="89.996864"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||||
<defs
|
<defs
|
||||||
id="defs4" />
|
id="defs4" />
|
||||||
<sodipodi:namedview
|
<sodipodi:namedview
|
||||||
|
@ -27,19 +27,22 @@
|
||||||
borderopacity="1.0"
|
borderopacity="1.0"
|
||||||
inkscape:pageopacity="0.0"
|
inkscape:pageopacity="0.0"
|
||||||
inkscape:pageshadow="2"
|
inkscape:pageshadow="2"
|
||||||
inkscape:zoom="15.839192"
|
inkscape:zoom="3.3180469"
|
||||||
inkscape:cx="101.80916"
|
inkscape:cx="168.92468"
|
||||||
inkscape:cy="48.17346"
|
inkscape:cy="113.47037"
|
||||||
inkscape:document-units="px"
|
inkscape:document-units="px"
|
||||||
inkscape:current-layer="layer1"
|
inkscape:current-layer="layer1"
|
||||||
showgrid="true"
|
showgrid="true"
|
||||||
inkscape:window-width="3840"
|
inkscape:window-width="1920"
|
||||||
inkscape:window-height="2032"
|
inkscape:window-height="1019"
|
||||||
inkscape:window-x="0"
|
inkscape:window-x="0"
|
||||||
inkscape:window-y="54"
|
inkscape:window-y="0"
|
||||||
inkscape:window-maximized="1"
|
inkscape:window-maximized="1"
|
||||||
showguides="true"
|
showguides="true"
|
||||||
inkscape:guide-bbox="true">
|
inkscape:guide-bbox="true"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1">
|
||||||
<inkscape:grid
|
<inkscape:grid
|
||||||
type="xygrid"
|
type="xygrid"
|
||||||
id="grid3004"
|
id="grid3004"
|
||||||
|
@ -464,5 +467,21 @@
|
||||||
style="fill:#464646;fill-opacity:1;stroke:none;stroke-width:4.28879309"
|
style="fill:#464646;fill-opacity:1;stroke:none;stroke-width:4.28879309"
|
||||||
id="path4819-7"
|
id="path4819-7"
|
||||||
transform="rotate(-111.82202)" />
|
transform="rotate(-111.82202)" />
|
||||||
|
<path
|
||||||
|
style="fill:#464646;fill-opacity:1;stroke:none;stroke-width:6.97516;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||||
|
inkscape:transform-center-x="-0.0017170804"
|
||||||
|
inkscape:transform-center-y="-1.1593678"
|
||||||
|
d="m 130.9794,1042.1863 2.18213,-8.2579 -6.37758,-5.1021 h 8.14486 l 2.85897,-8.6349 2.85166,8.6333 h 8.14451 l -6.38242,5.1066 2.17462,8.2601 -6.79622,-4.7336 z"
|
||||||
|
id="path4734"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="ccccccccccc" />
|
||||||
|
<path
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#464646;stroke-width:1;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||||
|
inkscape:transform-center-x="-0.0017170804"
|
||||||
|
inkscape:transform-center-y="-1.1593678"
|
||||||
|
d="m 170.9794,1042.1863 2.18213,-8.2579 -6.37758,-5.1021 h 8.14486 l 2.85897,-8.6349 2.85166,8.6333 h 8.14451 l -6.38242,5.1066 2.17462,8.2601 -6.79623,-4.7336 z"
|
||||||
|
id="path4734-3"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
sodipodi:nodetypes="ccccccccccc" />
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 36 KiB |
|
@ -1018,6 +1018,28 @@ L.U.AttributionControl = L.Control.Attribution.extend({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
L.U.StarControl = L.Control.extend({
|
||||||
|
options: {
|
||||||
|
position: 'topleft',
|
||||||
|
},
|
||||||
|
|
||||||
|
onAdd: function (map) {
|
||||||
|
var status = map.options.starred ? ' starred' : ''
|
||||||
|
var container = L.DomUtil.create(
|
||||||
|
'div',
|
||||||
|
'leaflet-control-star umap-control' + status
|
||||||
|
),
|
||||||
|
link = L.DomUtil.create('a', '', container)
|
||||||
|
link.href = '#'
|
||||||
|
link.title = L._('Star this map')
|
||||||
|
L.DomEvent.on(link, 'click', L.DomEvent.stop)
|
||||||
|
.on(link, 'click', map.star, map)
|
||||||
|
.on(link, 'dblclick', L.DomEvent.stopPropagation)
|
||||||
|
|
||||||
|
return container
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
L.U.Search = L.PhotonSearch.extend({
|
L.U.Search = L.PhotonSearch.extend({
|
||||||
initialize: function (map, input, options) {
|
initialize: function (map, input, options) {
|
||||||
L.PhotonSearch.prototype.initialize.call(this, map, input, options)
|
L.PhotonSearch.prototype.initialize.call(this, map, input, options)
|
||||||
|
|
|
@ -1060,6 +1060,10 @@ L.U.FormBuilder = L.FormBuilder.extend({
|
||||||
handler: 'DataLayersControl',
|
handler: 'DataLayersControl',
|
||||||
label: L._('Display the data layers control'),
|
label: L._('Display the data layers control'),
|
||||||
},
|
},
|
||||||
|
starControl: {
|
||||||
|
handler: 'ControlChoice',
|
||||||
|
label: L._('Display the star map button'),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
initialize: function (obj, fields, options) {
|
initialize: function (obj, fields, options) {
|
||||||
|
|
|
@ -60,6 +60,7 @@ L.U.Map.include({
|
||||||
'tilelayers',
|
'tilelayers',
|
||||||
'editinosm',
|
'editinosm',
|
||||||
'datalayers',
|
'datalayers',
|
||||||
|
'star',
|
||||||
],
|
],
|
||||||
|
|
||||||
initialize: function (el, geojson) {
|
initialize: function (el, geojson) {
|
||||||
|
@ -309,6 +310,7 @@ L.U.Map.include({
|
||||||
this._controls.search = new L.U.SearchControl()
|
this._controls.search = new L.U.SearchControl()
|
||||||
this._controls.embed = new L.Control.Embed(this, this.options.embedOptions)
|
this._controls.embed = new L.Control.Embed(this, this.options.embedOptions)
|
||||||
this._controls.tilelayers = new L.U.TileLayerControl(this)
|
this._controls.tilelayers = new L.U.TileLayerControl(this)
|
||||||
|
this._controls.star = new L.U.StarControl(this)
|
||||||
this._controls.editinosm = new L.Control.EditInOSM({
|
this._controls.editinosm = new L.Control.EditInOSM({
|
||||||
position: 'topleft',
|
position: 'topleft',
|
||||||
widgetOptions: {
|
widgetOptions: {
|
||||||
|
@ -1283,6 +1285,7 @@ L.U.Map.include({
|
||||||
'embedControl',
|
'embedControl',
|
||||||
'measureControl',
|
'measureControl',
|
||||||
'tilelayersControl',
|
'tilelayersControl',
|
||||||
|
'starControl',
|
||||||
'easing',
|
'easing',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -1369,6 +1372,28 @@ L.U.Map.include({
|
||||||
return (this.options.umap_id && this.getEditUrl()) || this.getCreateUrl()
|
return (this.options.umap_id && this.getEditUrl()) || this.getCreateUrl()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
star: function () {
|
||||||
|
if (!this.options.umap_id)
|
||||||
|
return this.ui.alert({
|
||||||
|
content: L._('Please save the map first'),
|
||||||
|
level: 'error',
|
||||||
|
})
|
||||||
|
let url = L.Util.template(this.options.urls.map_star, {
|
||||||
|
map_id: this.options.umap_id,
|
||||||
|
})
|
||||||
|
this.post(url, {
|
||||||
|
context: this,
|
||||||
|
callback: function (data) {
|
||||||
|
this.options.starred = data.starred
|
||||||
|
let msg = data.starred
|
||||||
|
? L._('Map has been starred')
|
||||||
|
: L._('Map has been unstarred')
|
||||||
|
this.ui.alert({ content: msg, level: 'info' })
|
||||||
|
this.renderControls()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
geometry: function () {
|
geometry: function () {
|
||||||
/* Return a GeoJSON geometry Object */
|
/* Return a GeoJSON geometry Object */
|
||||||
var latlng = this.latLng(this.options.center || this.getCenter())
|
var latlng = this.latLng(this.options.center || this.getCenter())
|
||||||
|
|
|
@ -88,6 +88,12 @@ a.umap-control-less {
|
||||||
background-position: -80px -161px;
|
background-position: -80px -161px;
|
||||||
box-shadow: 0 0 4px 0 black inset;
|
box-shadow: 0 0 4px 0 black inset;
|
||||||
}
|
}
|
||||||
|
.leaflet-control-star a {
|
||||||
|
background-position: -118px -160px;
|
||||||
|
}
|
||||||
|
.leaflet-control-star.starred a {
|
||||||
|
background-position: -158px -160px;
|
||||||
|
}
|
||||||
.leaflet-control-search a {
|
.leaflet-control-search a {
|
||||||
background-position: -41px -121px;
|
background-position: -41px -121px;
|
||||||
display: block;
|
display: block;
|
||||||
|
|
20
umap/templates/auth/user_stars.html
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{% extends "umap/content.html" %}
|
||||||
|
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block maincontent %}
|
||||||
|
<div class="col wide">
|
||||||
|
<h2 class="section">{% blocktrans %}Browse {{ current_user }}'s starred maps{% endblocktrans %}</h2>
|
||||||
|
</div>
|
||||||
|
<div class="wrapper">
|
||||||
|
<div class="map_list row">
|
||||||
|
{% if maps %}
|
||||||
|
{% include "umap/map_list.html" %}
|
||||||
|
{% else %}
|
||||||
|
<div>
|
||||||
|
{% blocktrans %}{{ current_user }} has no starred maps yet.{% endblocktrans %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock maincontent %}
|
|
@ -8,6 +8,7 @@
|
||||||
<ul>
|
<ul>
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<li><a href="{% url 'user_maps' user.username %}">{% trans "My maps" %} ({{ user }})</a></li>
|
<li><a href="{% url 'user_maps' user.username %}">{% trans "My maps" %} ({{ user }})</a></li>
|
||||||
|
<li><a href="{% url 'user_stars' user.username %}">{% trans "Starred maps" %}</a></li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li><a href="{% url 'login' %}" class="login">{% trans "Log in" %} / {% trans "Sign in" %}</a></li>
|
<li><a href="{% url 'login' %}" class="login">{% trans "Log in" %} / {% trans "Sign in" %}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -5,7 +5,7 @@ from django.contrib.auth import get_user_model
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from django.core.signing import Signer
|
from django.core.signing import Signer
|
||||||
from umap.models import DataLayer, Map
|
from umap.models import DataLayer, Map, Star
|
||||||
|
|
||||||
from .base import login_required
|
from .base import login_required
|
||||||
|
|
||||||
|
@ -539,3 +539,33 @@ def test_search(client, map):
|
||||||
url = reverse("search")
|
url = reverse("search")
|
||||||
response = client.get(url + "?q=Blé")
|
response = client.get(url + "?q=Blé")
|
||||||
assert "Blé dur" in response.content.decode()
|
assert "Blé dur" in response.content.decode()
|
||||||
|
|
||||||
|
|
||||||
|
def test_authenticated_user_can_star_map(client, map, user):
|
||||||
|
url = reverse('map_star', args=(map.pk,))
|
||||||
|
client.login(username=user.username, password="123123")
|
||||||
|
assert Star.objects.filter(by=user).count() == 0
|
||||||
|
response = client.post(url)
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert Star.objects.filter(by=user).count() == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_anonymous_cannot_star_map(client, map):
|
||||||
|
url = reverse('map_star', args=(map.pk,))
|
||||||
|
assert Star.objects.count() == 0
|
||||||
|
response = client.post(url)
|
||||||
|
assert response.status_code == 302
|
||||||
|
assert "login" in response["Location"]
|
||||||
|
assert Star.objects.count() == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_can_see_their_star(client, map, user):
|
||||||
|
url = reverse('map_star', args=(map.pk,))
|
||||||
|
client.login(username=user.username, password="123123")
|
||||||
|
assert Star.objects.filter(by=user).count() == 0
|
||||||
|
response = client.post(url)
|
||||||
|
assert response.status_code == 200
|
||||||
|
url = reverse('user_stars', args=(user.username,))
|
||||||
|
response = client.get(url)
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert map.name in response.content.decode()
|
||||||
|
|
10
umap/urls.py
|
@ -4,6 +4,7 @@ from django.conf.urls.i18n import i18n_patterns
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.auth import views as auth_views
|
from django.contrib.auth import views as auth_views
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||||
from django.views.decorators.cache import cache_control, cache_page, never_cache
|
from django.views.decorators.cache import cache_control, cache_page, never_cache
|
||||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||||
|
@ -92,6 +93,14 @@ i18n_urls += decorated_patterns(
|
||||||
[login_required_if_not_anonymous_allowed, never_cache],
|
[login_required_if_not_anonymous_allowed, never_cache],
|
||||||
re_path(r"^map/create/$", views.MapCreate.as_view(), name="map_create"),
|
re_path(r"^map/create/$", views.MapCreate.as_view(), name="map_create"),
|
||||||
)
|
)
|
||||||
|
i18n_urls += decorated_patterns(
|
||||||
|
[login_required],
|
||||||
|
re_path(
|
||||||
|
r'^map/(?P<map_id>[\d]+)/star/$',
|
||||||
|
views.ToggleMapStarStatus.as_view(),
|
||||||
|
name='map_star'
|
||||||
|
),
|
||||||
|
)
|
||||||
i18n_urls += decorated_patterns(
|
i18n_urls += decorated_patterns(
|
||||||
[map_permissions_check, never_cache],
|
[map_permissions_check, never_cache],
|
||||||
re_path(
|
re_path(
|
||||||
|
@ -142,6 +151,7 @@ urlpatterns += i18n_patterns(
|
||||||
),
|
),
|
||||||
re_path(r"^search/$", views.search, name="search"),
|
re_path(r"^search/$", views.search, name="search"),
|
||||||
re_path(r"^about/$", views.about, name="about"),
|
re_path(r"^about/$", views.about, name="about"),
|
||||||
|
re_path(r"^user/(?P<username>.+)/stars/$", views.user_stars, name='user_stars'),
|
||||||
re_path(r"^user/(?P<username>.+)/$", views.user_maps, name="user_maps"),
|
re_path(r"^user/(?P<username>.+)/$", views.user_maps, name="user_maps"),
|
||||||
re_path(r"", include(i18n_urls)),
|
re_path(r"", include(i18n_urls)),
|
||||||
)
|
)
|
||||||
|
|
|
@ -45,7 +45,7 @@ from .forms import (
|
||||||
MapSettingsForm,
|
MapSettingsForm,
|
||||||
UpdateMapPermissionsForm,
|
UpdateMapPermissionsForm,
|
||||||
)
|
)
|
||||||
from .models import DataLayer, Licence, Map, Pictogram, TileLayer
|
from .models import DataLayer, Licence, Map, Pictogram, Star, TileLayer
|
||||||
from .utils import get_uri_template, gzip_file, is_ajax
|
from .utils import get_uri_template, gzip_file, is_ajax
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -154,18 +154,26 @@ class UserMaps(DetailView, PaginatorMixin):
|
||||||
list_template_name = "umap/map_list.html"
|
list_template_name = "umap/map_list.html"
|
||||||
context_object_name = "current_user"
|
context_object_name = "current_user"
|
||||||
|
|
||||||
|
def is_owner(self):
|
||||||
|
return self.request.user == self.object
|
||||||
|
|
||||||
|
@property
|
||||||
|
def per_page(self):
|
||||||
|
if self.is_owner():
|
||||||
|
return settings.UMAP_MAPS_PER_PAGE_OWNER
|
||||||
|
return settings.UMAP_MAPS_PER_PAGE
|
||||||
|
|
||||||
|
def get_map_queryset(self):
|
||||||
|
return Map.objects if self.is_owner() else Map.public
|
||||||
|
|
||||||
|
def get_maps(self):
|
||||||
|
qs = self.get_map_queryset()
|
||||||
|
qs = qs.filter(Q(owner=self.object) | Q(editors=self.object))
|
||||||
|
return qs.distinct().order_by("-modified_at")
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
owner = self.request.user == self.object
|
kwargs.update({"maps": self.paginate(self.get_maps(), self.per_page)})
|
||||||
manager = Map.objects if owner else Map.public
|
return super().get_context_data(**kwargs)
|
||||||
maps = manager.filter(Q(owner=self.object) | Q(editors=self.object))
|
|
||||||
if owner:
|
|
||||||
per_page = settings.UMAP_MAPS_PER_PAGE_OWNER
|
|
||||||
else:
|
|
||||||
per_page = settings.UMAP_MAPS_PER_PAGE
|
|
||||||
maps = maps.distinct().order_by("-modified_at")
|
|
||||||
maps = self.paginate(maps, per_page)
|
|
||||||
kwargs.update({"maps": maps})
|
|
||||||
return super(UserMaps, self).get_context_data(**kwargs)
|
|
||||||
|
|
||||||
def get_template_names(self):
|
def get_template_names(self):
|
||||||
"""
|
"""
|
||||||
|
@ -180,6 +188,19 @@ class UserMaps(DetailView, PaginatorMixin):
|
||||||
user_maps = UserMaps.as_view()
|
user_maps = UserMaps.as_view()
|
||||||
|
|
||||||
|
|
||||||
|
class UserStars(UserMaps):
|
||||||
|
template_name = "auth/user_stars.html"
|
||||||
|
|
||||||
|
def get_maps(self):
|
||||||
|
qs = self.get_map_queryset()
|
||||||
|
stars = Star.objects.filter(by=self.object).values("map")
|
||||||
|
qs = qs.filter(pk__in=stars)
|
||||||
|
return qs.order_by("-modified_at")
|
||||||
|
|
||||||
|
|
||||||
|
user_stars = UserStars.as_view()
|
||||||
|
|
||||||
|
|
||||||
class Search(TemplateView, PaginatorMixin):
|
class Search(TemplateView, PaginatorMixin):
|
||||||
template_name = "umap/search.html"
|
template_name = "umap/search.html"
|
||||||
list_template_name = "umap/map_list.html"
|
list_template_name = "umap/map_list.html"
|
||||||
|
@ -360,6 +381,7 @@ class MapDetailMixin:
|
||||||
"allowEdit": self.is_edit_allowed(),
|
"allowEdit": self.is_edit_allowed(),
|
||||||
"default_iconUrl": "%sumap/img/marker.png" % settings.STATIC_URL, # noqa
|
"default_iconUrl": "%sumap/img/marker.png" % settings.STATIC_URL, # noqa
|
||||||
"umap_id": self.get_umap_id(),
|
"umap_id": self.get_umap_id(),
|
||||||
|
'starred': self.is_starred(),
|
||||||
"licences": dict((l.name, l.json) for l in Licence.objects.all()),
|
"licences": dict((l.name, l.json) for l in Licence.objects.all()),
|
||||||
"edit_statuses": [(i, str(label)) for i, label in Map.EDIT_STATUS],
|
"edit_statuses": [(i, str(label)) for i, label in Map.EDIT_STATUS],
|
||||||
"share_statuses": [
|
"share_statuses": [
|
||||||
|
@ -404,6 +426,9 @@ class MapDetailMixin:
|
||||||
def get_umap_id(self):
|
def get_umap_id(self):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def is_starred(self):
|
||||||
|
return False
|
||||||
|
|
||||||
def get_geojson(self):
|
def get_geojson(self):
|
||||||
return {
|
return {
|
||||||
"geometry": {
|
"geometry": {
|
||||||
|
@ -489,6 +514,12 @@ class MapView(MapDetailMixin, PermissionsMixin, DetailView):
|
||||||
map_settings["properties"]["permissions"] = self.get_permissions()
|
map_settings["properties"]["permissions"] = self.get_permissions()
|
||||||
return map_settings
|
return map_settings
|
||||||
|
|
||||||
|
def is_starred(self):
|
||||||
|
user = self.request.user
|
||||||
|
if not user.is_authenticated:
|
||||||
|
return False
|
||||||
|
return Star.objects.filter(by=user, map=self.object).exists()
|
||||||
|
|
||||||
|
|
||||||
class MapViewGeoJSON(MapView):
|
class MapViewGeoJSON(MapView):
|
||||||
def get_canonical_url(self):
|
def get_canonical_url(self):
|
||||||
|
@ -631,6 +662,20 @@ class MapClone(PermissionsMixin, View):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class ToggleMapStarStatus(View):
|
||||||
|
|
||||||
|
def post(self, *args, **kwargs):
|
||||||
|
map_inst = get_object_or_404(Map, pk=kwargs['map_id'])
|
||||||
|
qs = Star.objects.filter(map=map_inst, by=self.request.user)
|
||||||
|
if qs.exists():
|
||||||
|
qs.delete()
|
||||||
|
status = False
|
||||||
|
else:
|
||||||
|
Star.objects.create(map=map_inst, by=self.request.user)
|
||||||
|
status = True
|
||||||
|
return simple_json_response(starred=status)
|
||||||
|
|
||||||
|
|
||||||
class MapShortUrl(RedirectView):
|
class MapShortUrl(RedirectView):
|
||||||
query_string = True
|
query_string = True
|
||||||
permanent = True
|
permanent = True
|
||||||
|
|