Skip to content

Commit

Permalink
Merge pull request #683 from umap-project/fav
Browse files Browse the repository at this point in the history
Allow to star maps and retrieve starred maps
  • Loading branch information
yohanboniface committed May 15, 2023
2 parents 70c7445 + a2b1b7b commit 317a8ba
Show file tree
Hide file tree
Showing 15 changed files with 246 additions and 33 deletions.
25 changes: 25 additions & 0 deletions umap/migrations/0009_star.py
Original file line number Diff line number Diff line change
@@ -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')),
],
),
]
6 changes: 6 additions & 0 deletions umap/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,3 +349,9 @@ def purge_old_versions(self):
self.geojson.storage.delete(path)
except FileNotFoundError:
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)
Binary file modified umap/static/umap/img/24-white.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 4 additions & 4 deletions umap/static/umap/img/24-white.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified umap/static/umap/img/24.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
51 changes: 35 additions & 16 deletions umap/static/umap/img/24.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions umap/static/umap/js/umap.controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -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({
initialize: function (map, input, options) {
L.PhotonSearch.prototype.initialize.call(this, map, input, options)
Expand Down
4 changes: 4 additions & 0 deletions umap/static/umap/js/umap.forms.js
Original file line number Diff line number Diff line change
Expand Up @@ -1060,6 +1060,10 @@ L.U.FormBuilder = L.FormBuilder.extend({
handler: 'DataLayersControl',
label: L._('Display the data layers control'),
},
starControl: {
handler: 'ControlChoice',
label: L._('Display the star map button'),
},
},

initialize: function (obj, fields, options) {
Expand Down
25 changes: 25 additions & 0 deletions umap/static/umap/js/umap.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ L.U.Map.include({
'tilelayers',
'editinosm',
'datalayers',
'star',
],

initialize: function (el, geojson) {
Expand Down Expand Up @@ -309,6 +310,7 @@ L.U.Map.include({
this._controls.search = new L.U.SearchControl()
this._controls.embed = new L.Control.Embed(this, this.options.embedOptions)
this._controls.tilelayers = new L.U.TileLayerControl(this)
this._controls.star = new L.U.StarControl(this)
this._controls.editinosm = new L.Control.EditInOSM({
position: 'topleft',
widgetOptions: {
Expand Down Expand Up @@ -1283,6 +1285,7 @@ L.U.Map.include({
'embedControl',
'measureControl',
'tilelayersControl',
'starControl',
'easing',
],

Expand Down Expand Up @@ -1369,6 +1372,28 @@ L.U.Map.include({
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 () {
/* Return a GeoJSON geometry Object */
var latlng = this.latLng(this.options.center || this.getCenter())
Expand Down
6 changes: 6 additions & 0 deletions umap/static/umap/map.css
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ a.umap-control-less {
background-position: -80px -161px;
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 {
background-position: -41px -121px;
display: block;
Expand Down
20 changes: 20 additions & 0 deletions umap/templates/auth/user_stars.html
Original file line number Diff line number Diff line change
@@ -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 %}
1 change: 1 addition & 0 deletions umap/templates/umap/navigation.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ <h1><a href="/">{{ title }}</a></h1>
<ul>
{% if user.is_authenticated %}
<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 %}
<li><a href="{% url 'login' %}" class="login">{% trans "Log in" %} / {% trans "Sign in" %}</a></li>
{% endif %}
Expand Down
32 changes: 31 additions & 1 deletion umap/tests/test_map_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from django.urls import reverse

from django.core.signing import Signer
from umap.models import DataLayer, Map
from umap.models import DataLayer, Map, Star

from .base import login_required

Expand Down Expand Up @@ -539,3 +539,33 @@ def test_search(client, map):
url = reverse("search")
response = client.get(url + "?q=Blé")
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 changes: 10 additions & 0 deletions umap/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from django.conf.urls.static import static
from django.contrib import admin
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.views.decorators.cache import cache_control, cache_page, never_cache
from django.views.decorators.csrf import ensure_csrf_cookie
Expand Down Expand Up @@ -92,6 +93,14 @@
[login_required_if_not_anonymous_allowed, never_cache],
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(
[map_permissions_check, never_cache],
re_path(
Expand Down Expand Up @@ -142,6 +151,7 @@
),
re_path(r"^search/$", views.search, name="search"),
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"", include(i18n_urls)),
)
Expand Down
Loading

0 comments on commit 317a8ba

Please sign in to comment.