Skip to content

Commit

Permalink
Allow configuring landing page for unauthenticated users (#808)
Browse files Browse the repository at this point in the history
* allow configuring landing page

* add tests
  • Loading branch information
sissbruecker authored Aug 31, 2024
1 parent 36749c3 commit 5eadb3e
Show file tree
Hide file tree
Showing 12 changed files with 261 additions and 46 deletions.
38 changes: 38 additions & 0 deletions bookmarks/migrations/0037_globalsettings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Generated by Django 5.0.8 on 2024-08-31 12:39

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("bookmarks", "0036_userprofile_auto_tagging_rules"),
]

operations = [
migrations.CreateModel(
name="GlobalSettings",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"landing_page",
models.CharField(
choices=[
("login", "Login"),
("shared_bookmarks", "Shared Bookmarks"),
],
default="login",
max_length=50,
),
),
],
),
]
37 changes: 37 additions & 0 deletions bookmarks/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -492,3 +492,40 @@ def generate_key(cls):

def __str__(self):
return self.key


class GlobalSettings(models.Model):
LANDING_PAGE_LOGIN = "login"
LANDING_PAGE_SHARED_BOOKMARKS = "shared_bookmarks"
LANDING_PAGE_CHOICES = [
(LANDING_PAGE_LOGIN, "Login"),
(LANDING_PAGE_SHARED_BOOKMARKS, "Shared Bookmarks"),
]

landing_page = models.CharField(
max_length=50,
choices=LANDING_PAGE_CHOICES,
blank=False,
default=LANDING_PAGE_LOGIN,
)

@classmethod
def get(cls):
instance = GlobalSettings.objects.first()
if not instance:
instance = GlobalSettings()
instance.save()
return instance

def save(self, *args, **kwargs):
if not self.pk and GlobalSettings.objects.exists():
raise Exception("There is already one instance of GlobalSettings")
return super(GlobalSettings, self).save(*args, **kwargs)


class GlobalSettingsForm(forms.ModelForm):
class Meta:
model = GlobalSettings
fields = [
"landing_page",
]
8 changes: 4 additions & 4 deletions bookmarks/templates/bookmarks/layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -114,16 +114,16 @@
</div>
{% endif %}
<div class="d-flex justify-between">
<a href="{% url 'bookmarks:index' %}" class="d-flex align-center">
<a href="{% url 'bookmarks:root' %}" class="d-flex align-center">
<img class="logo" src="{% static 'logo.png' %}" alt="Application logo">
<h1>LINKDING</h1>
</a>
{% if request.user.is_authenticated %}
{# Only show nav items menu when logged in #}
{% include 'bookmarks/nav_menu.html' %}
{% elif has_public_shares %}
{# Otherwise show link to shared bookmarks if there are publicly shared bookmarks #}
<a href="{% url 'bookmarks:shared' %}" class="btn btn-link">Shared bookmarks</a>
{% else %}
{# Otherwise show login link #}
<a href="{% url 'login' %}" class="btn btn-link">Login</a>
{% endif %}
</div>
</header>
Expand Down
34 changes: 28 additions & 6 deletions bookmarks/templates/settings/general.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@
{% include 'settings/nav.html' %}

{# Profile section #}
{% if success_message %}
<div class="toast toast-success mb-4">{{ success_message }}</div>
{% endif %}
{% if error_message %}
<div class="toast toast-error mb-4">{{ error_message }}</div>
{% endif %}

<section class="content-area">
{% if success_message %}
<div class="toast toast-success mb-4">{{ success_message }}</div>
{% endif %}
{% if error_message %}
<div class="toast toast-error mb-4">{{ error_message }}</div>
{% endif %}
<h2>Profile</h2>
<p>
<a href="{% url 'change_password' %}">Change password</a>
Expand Down Expand Up @@ -238,6 +239,27 @@ <h2>Profile</h2>
</form>
</section>

{# Global settings section #}
{% if global_settings_form %}
<section class="content-area">
<h2>Global settings</h2>
<form action="{% url 'bookmarks:settings.general' %}" method="post" novalidate>
{% csrf_token %}
<div class="form-group">
<label for="{{ global_settings_form.landing_page.id_for_label }}" class="form-label">Landing page</label>
{{ global_settings_form.landing_page|add_class:"form-select width-25 width-sm-100" }}
<div class="form-input-hint">
The page that unauthorized users are redirected to when accessing the root URL.
</div>
</div>

<div class="form-group">
<input type="submit" name="update_global_settings" value="Save" class="btn btn-primary mt-2">
</div>
</form>
</section>
{% endif %}

{# Import section #}
<section class="content-area">
<h2>Import</h2>
Expand Down
5 changes: 5 additions & 0 deletions bookmarks/tests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ def get_or_create_test_user(self):

return self.user

def setup_superuser(self):
return User.objects.create_superuser(
"superuser", "superuser@example.com", "password123"
)

def setup_bookmark(
self,
is_archived: bool = False,
Expand Down
29 changes: 0 additions & 29 deletions bookmarks/tests/test_anonymous_view.py

This file was deleted.

40 changes: 40 additions & 0 deletions bookmarks/tests/test_root_view.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from django.test import TestCase
from django.urls import reverse

from bookmarks.models import GlobalSettings
from bookmarks.tests.helpers import BookmarkFactoryMixin


class AnonymousViewTestCase(TestCase, BookmarkFactoryMixin):
def test_unauthenticated_user_redirect_to_login_by_default(self):
response = self.client.get(reverse("bookmarks:root"))
self.assertRedirects(response, reverse("login"))

def test_unauthenticated_redirect_to_shared_bookmarks_if_configured_in_global_settings(
self,
):
settings = GlobalSettings.get()
settings.landing_page = GlobalSettings.LANDING_PAGE_SHARED_BOOKMARKS
settings.save()

response = self.client.get(reverse("bookmarks:root"))
self.assertRedirects(response, reverse("bookmarks:shared"))

def test_authenticated_user_always_redirected_to_bookmarks(self):
self.client.force_login(self.get_or_create_test_user())

response = self.client.get(reverse("bookmarks:root"))
self.assertRedirects(response, reverse("bookmarks:index"))

settings = GlobalSettings.get()
settings.landing_page = GlobalSettings.LANDING_PAGE_SHARED_BOOKMARKS
settings.save()

response = self.client.get(reverse("bookmarks:root"))
self.assertRedirects(response, reverse("bookmarks:index"))

settings.landing_page = GlobalSettings.LANDING_PAGE_LOGIN
settings.save()

response = self.client.get(reverse("bookmarks:root"))
self.assertRedirects(response, reverse("bookmarks:index"))
62 changes: 61 additions & 1 deletion bookmarks/tests/test_settings_general_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from django.urls import reverse
from requests import RequestException

from bookmarks.models import UserProfile
from bookmarks.models import UserProfile, GlobalSettings
from bookmarks.services import tasks
from bookmarks.tests.helpers import BookmarkFactoryMixin
from bookmarks.views.settings import app_version, get_version_info
Expand Down Expand Up @@ -465,3 +465,63 @@ def test_create_missing_html_snapshots_should_not_be_called_without_respective_f
self.assertSuccessMessage(
html, "Queued 5 missing snapshots. This may take a while...", count=0
)

def test_update_global_settings(self):
superuser = self.setup_superuser()
self.client.force_login(superuser)

form_data = {
"update_global_settings": "",
"landing_page": GlobalSettings.LANDING_PAGE_SHARED_BOOKMARKS,
}
response = self.client.post(reverse("bookmarks:settings.general"), form_data)
self.assertEqual(response.status_code, 200)
self.assertSuccessMessage(response.content.decode(), "Global settings updated")

global_settings = GlobalSettings.get()
self.assertEqual(global_settings.landing_page, form_data["landing_page"])

def test_update_global_settings_should_not_be_called_without_respective_form_action(
self,
):
superuser = self.setup_superuser()
self.client.force_login(superuser)

form_data = {
"landing_page": GlobalSettings.LANDING_PAGE_SHARED_BOOKMARKS,
}
response = self.client.post(reverse("bookmarks:settings.general"), form_data)
self.assertEqual(response.status_code, 200)
self.assertSuccessMessage(
response.content.decode(), "Global settings updated", count=0
)

def test_update_global_settings_checks_for_superuser(self):
form_data = {
"update_global_settings": "",
"landing_page": GlobalSettings.LANDING_PAGE_SHARED_BOOKMARKS,
}
response = self.client.post(reverse("bookmarks:settings.general"), form_data)
self.assertEqual(response.status_code, 403)

def test_global_settings_only_visible_for_superuser(self):
response = self.client.get(reverse("bookmarks:settings.general"))
html = response.content.decode()

self.assertInHTML(
"<h2>Global settings</h2>",
html,
count=0,
)

superuser = self.setup_superuser()
self.client.force_login(superuser)

response = self.client.get(reverse("bookmarks:settings.general"))
html = response.content.decode()

self.assertInHTML(
"<h2>Global settings</h2>",
html,
count=1,
)
7 changes: 2 additions & 5 deletions bookmarks/urls.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from django.urls import path, include
from django.urls import re_path
from django.views.generic import RedirectView

from bookmarks import views
from bookmarks.api.routes import router
Expand All @@ -14,10 +13,8 @@

app_name = "bookmarks"
urlpatterns = [
# Redirect root to bookmarks index
re_path(
r"^$", RedirectView.as_view(pattern_name="bookmarks:index", permanent=False)
),
# Root view handling redirection based on user authentication
re_path(r"^$", views.root, name="root"),
# Bookmarks
path("bookmarks", views.bookmarks.index, name="index"),
path("bookmarks/action", views.bookmarks.index_action, name="index.action"),
Expand Down
1 change: 1 addition & 0 deletions bookmarks/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
from .toasts import *
from .health import health
from .manifest import manifest
from .root import root
18 changes: 18 additions & 0 deletions bookmarks/views/root.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from django.http import HttpResponseRedirect
from django.urls import reverse

from bookmarks.models import GlobalSettings


def root(request):
# Redirect unauthenticated users to the configured landing page
if not request.user.is_authenticated:
settings = GlobalSettings.get()

if settings.landing_page == GlobalSettings.LANDING_PAGE_SHARED_BOOKMARKS:
return HttpResponseRedirect(reverse("bookmarks:shared"))
else:
return HttpResponseRedirect(reverse("login"))

# Redirect authenticated users to the bookmarks page
return HttpResponseRedirect(reverse("bookmarks:index"))
Loading

0 comments on commit 5eadb3e

Please sign in to comment.