Merge pull request #1269 from umap-project/edit-username
Very lite "My Profile" page to allow changing username
This commit is contained in:
commit
9b3fe26acd
9 changed files with 148 additions and 4 deletions
|
@ -89,3 +89,10 @@ class MapSettingsForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
fields = ('settings', 'name', 'center', 'slug')
|
fields = ('settings', 'name', 'center', 'slug')
|
||||||
model = Map
|
model = Map
|
||||||
|
|
||||||
|
|
||||||
|
class UserProfileForm(forms.ModelForm):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = ('username', 'first_name', 'last_name')
|
||||||
|
|
|
@ -129,6 +129,8 @@ DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
||||||
|
|
||||||
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||||
FROM_EMAIL = None
|
FROM_EMAIL = None
|
||||||
|
# https://docs.djangoproject.com/en/4.2/releases/4.1/#forms
|
||||||
|
FORM_RENDERER = "django.forms.renderers.DjangoDivFormRenderer"
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Calculation of directories relative to the project module location
|
# Calculation of directories relative to the project module location
|
||||||
|
@ -262,8 +264,6 @@ LEAFLET_ZOOM = env.int('LEAFLET_ZOOM', default=6)
|
||||||
COMPRESS_ENABLED = True
|
COMPRESS_ENABLED = True
|
||||||
COMPRESS_OFFLINE = True
|
COMPRESS_OFFLINE = True
|
||||||
|
|
||||||
SOCIAL_AUTH_NO_DEFAULT_PROTECTED_USER_FIELDS = True
|
|
||||||
SOCIAL_AUTH_PROTECTED_USER_FIELDS = ("id", )
|
|
||||||
LOGIN_URL = "login"
|
LOGIN_URL = "login"
|
||||||
SOCIAL_AUTH_LOGIN_REDIRECT_URL = "/login/popup/end/"
|
SOCIAL_AUTH_LOGIN_REDIRECT_URL = "/login/popup/end/"
|
||||||
|
|
||||||
|
|
|
@ -194,6 +194,10 @@ select[multiple="multiple"] {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
border-radius: 0 2px;
|
border-radius: 0 2px;
|
||||||
}
|
}
|
||||||
|
.content .helptext {
|
||||||
|
background-color: #eee;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
input + .help-text {
|
input + .help-text {
|
||||||
margin-top: -14px;
|
margin-top: -14px;
|
||||||
}
|
}
|
||||||
|
@ -214,6 +218,9 @@ label {
|
||||||
line-height: 21px;
|
line-height: 21px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
.content label {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
input[type="checkbox"] + label {
|
input[type="checkbox"] + label {
|
||||||
display: inline;
|
display: inline;
|
||||||
padding: 0 14px;
|
padding: 0 14px;
|
||||||
|
|
|
@ -133,6 +133,13 @@ h2.section {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding-top: 28px;
|
padding-top: 28px;
|
||||||
}
|
}
|
||||||
|
h2.tabs a {
|
||||||
|
font-weight: normal;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
h2.tabs a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
.showcase-map .map_fragment {
|
.showcase-map .map_fragment {
|
||||||
height: 400px;
|
height: 400px;
|
||||||
}
|
}
|
||||||
|
|
50
umap/templates/auth/user_form.html
Normal file
50
umap/templates/auth/user_form.html
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
{% extends "umap/content.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block maincontent %}
|
||||||
|
<div class="col wide">
|
||||||
|
<h2 class="section tabs">
|
||||||
|
<a href="{% url 'user_dashboard' %}">{% trans "My dashboard" %}</a> | {% trans "My profile" %}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div class="wrapper">
|
||||||
|
<div class="row">
|
||||||
|
{% if form.non_field_errors %}
|
||||||
|
<ul class="form-errors">
|
||||||
|
{% for error in form.non_field_errors %}<li>{{ error }}</li>{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
<form id="user_form" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form }}
|
||||||
|
<input type="submit" value="{% trans 'Save' %}" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% if backends.backends|length %}
|
||||||
|
<div class="row">
|
||||||
|
<h3>{% trans "Your current providers" %}</h3>
|
||||||
|
<ul>
|
||||||
|
{% for name in providers %}<li>{{ name|title }}</li>{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<h3>{% trans "Connect to another provider" %}</h3>
|
||||||
|
<p>
|
||||||
|
{% blocktrans %}It's a good habit to connect your account to more than one provider, in case one provider becomes unavailable, temporarily or even permanently.{% endblocktrans %}
|
||||||
|
</p>
|
||||||
|
<div>
|
||||||
|
<ul class="login-grid block-grid">
|
||||||
|
{% for name in backends.backends %}
|
||||||
|
{% if name not in providers %}
|
||||||
|
<li>
|
||||||
|
<a href="{% url "social:begin" name %}"
|
||||||
|
class="umap-login-popup login-{{ name }}"
|
||||||
|
title="{{ name|title }}"></a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock maincontent %}
|
|
@ -6,7 +6,9 @@
|
||||||
{% block maincontent %}
|
{% block maincontent %}
|
||||||
{% trans "Search my maps" as placeholder %}
|
{% trans "Search my maps" as placeholder %}
|
||||||
<div class="col wide">
|
<div class="col wide">
|
||||||
<h2 class="section">{% trans "My dashboard" %}</h2>
|
<h2 class="section tabs">
|
||||||
|
{% trans "My dashboard" %} | <a href="{% url 'user_profile' %}">{% trans "My profile" %}</a>
|
||||||
|
</h2>
|
||||||
{% include "umap/search_bar.html" with action=request.get_full_path placeholder=placeholder %}
|
{% include "umap/search_bar.html" with action=request.get_full_path placeholder=placeholder %}
|
||||||
</div>
|
</div>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
|
|
|
@ -11,6 +11,8 @@ from django.test import RequestFactory
|
||||||
from umap import VERSION
|
from umap import VERSION
|
||||||
from umap.views import validate_url
|
from umap.views import validate_url
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
def get(target="http://osm.org/georss.xml", verb="get", **kwargs):
|
def get(target="http://osm.org/georss.xml", verb="get", **kwargs):
|
||||||
defaults = {
|
defaults = {
|
||||||
|
@ -141,7 +143,6 @@ def test_login_contains_form_if_enabled(client, settings):
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_can_login_with_username_and_password_if_enabled(client, settings):
|
def test_can_login_with_username_and_password_if_enabled(client, settings):
|
||||||
settings.ENABLE_ACCOUNT_LOGIN = True
|
settings.ENABLE_ACCOUNT_LOGIN = True
|
||||||
User = get_user_model()
|
|
||||||
user = User.objects.create(username="test")
|
user = User.objects.create(username="test")
|
||||||
user.set_password("test")
|
user.set_password("test")
|
||||||
user.save()
|
user.save()
|
||||||
|
@ -279,3 +280,49 @@ def test_logout_should_return_redirect(client, user, settings):
|
||||||
response = client.get(reverse("logout"))
|
response = client.get(reverse("logout"))
|
||||||
assert response.status_code == 302
|
assert response.status_code == 302
|
||||||
assert response["Location"] == "/"
|
assert response["Location"] == "/"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_user_profile_is_restricted_to_logged_in(client):
|
||||||
|
response = client.get(reverse("user_profile"))
|
||||||
|
assert response.status_code == 302
|
||||||
|
assert response["Location"] == "/en/login/?next=/en/me/profile"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_user_profile_allows_to_edit_username(client, map):
|
||||||
|
client.login(username=map.owner.username, password="123123")
|
||||||
|
new_name = "newname"
|
||||||
|
response = client.post(
|
||||||
|
reverse("user_profile"), data={"username": new_name}, follow=True
|
||||||
|
)
|
||||||
|
assert response.status_code == 200
|
||||||
|
user = User.objects.get(pk=map.owner.pk)
|
||||||
|
assert user.username == new_name
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_user_profile_cannot_set_to_existing_username(client, map, user2):
|
||||||
|
client.login(username=map.owner.username, password="123123")
|
||||||
|
response = client.post(
|
||||||
|
reverse("user_profile"), data={"username": user2.username}, follow=True
|
||||||
|
)
|
||||||
|
assert response.status_code == 200
|
||||||
|
user = User.objects.get(pk=map.owner.pk)
|
||||||
|
assert user.username == map.owner.username
|
||||||
|
assert user.username != user2.username
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_user_profile_does_not_allow_to_edit_other_fields(client, map):
|
||||||
|
client.login(username=map.owner.username, password="123123")
|
||||||
|
new_email = "foo@bar.com"
|
||||||
|
response = client.post(
|
||||||
|
reverse("user_profile"),
|
||||||
|
data={"username": new_email, "is_superuser": True},
|
||||||
|
follow=True,
|
||||||
|
)
|
||||||
|
assert response.status_code == 200
|
||||||
|
user = User.objects.get(pk=map.owner.pk)
|
||||||
|
assert user.email != new_email
|
||||||
|
assert user.is_superuser is False
|
||||||
|
|
|
@ -103,6 +103,11 @@ i18n_urls += decorated_patterns(
|
||||||
views.user_dashboard,
|
views.user_dashboard,
|
||||||
name="user_dashboard",
|
name="user_dashboard",
|
||||||
),
|
),
|
||||||
|
re_path(
|
||||||
|
r"^me/profile$",
|
||||||
|
views.user_profile,
|
||||||
|
name="user_profile",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
map_urls = [
|
map_urls = [
|
||||||
re_path(
|
re_path(
|
||||||
|
|
|
@ -50,6 +50,7 @@ from .forms import (
|
||||||
MapSettingsForm,
|
MapSettingsForm,
|
||||||
SendLinkForm,
|
SendLinkForm,
|
||||||
UpdateMapPermissionsForm,
|
UpdateMapPermissionsForm,
|
||||||
|
UserProfileForm,
|
||||||
)
|
)
|
||||||
from .models import DataLayer, Licence, Map, Pictogram, Star, 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
|
||||||
|
@ -164,6 +165,24 @@ class About(Home):
|
||||||
about = About.as_view()
|
about = About.as_view()
|
||||||
|
|
||||||
|
|
||||||
|
class UserProfile(UpdateView):
|
||||||
|
model = User
|
||||||
|
form_class = UserProfileForm
|
||||||
|
success_url = reverse_lazy("user_profile")
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
return self.get_queryset().get(pk=self.request.user.pk)
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
kwargs.update(
|
||||||
|
{"providers": self.object.social_auth.values_list("provider", flat=True)}
|
||||||
|
)
|
||||||
|
return super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
user_profile = UserProfile.as_view()
|
||||||
|
|
||||||
|
|
||||||
class UserMaps(PaginatorMixin, DetailView):
|
class UserMaps(PaginatorMixin, DetailView):
|
||||||
model = User
|
model = User
|
||||||
slug_url_kwarg = "identifier"
|
slug_url_kwarg = "identifier"
|
||||||
|
|
Loading…
Reference in a new issue