Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
97f96de
Add danger note to manual installation docs
henhuy Mar 5, 2026
a5a1ec1
Rename groups into organizations
henhuy Mar 6, 2026
12ed113
Refactored toasts using django messages framework
henhuy Mar 6, 2026
1e82861
Fix error caused by organization permissions in user tables
henhuy Mar 6, 2026
8fb1ac9
Merge PartialOrganizationEditFormView into OrganizationManagementView
henhuy Mar 6, 2026
7223aae
Refactor PartialOrganizationMemberManagementView to return HTML inste…
henhuy Mar 9, 2026
bddf640
Merge organization member invite into PartialOrganizationMemberManage…
henhuy Mar 9, 2026
504963a
Refactor organization member count into model
henhuy Mar 9, 2026
2044133
Remove unused code
henhuy Mar 9, 2026
122abe6
Refactor organization leave to use HTMX properly
henhuy Mar 9, 2026
b9f7e1c
Refactor organization deletion into own function
henhuy Mar 9, 2026
b1518da
Remove unnecessary HTMX config in JS
henhuy Mar 9, 2026
2d5e502
Remove unused template list_memberships.html
henhuy Mar 9, 2026
087a226
Rename organization views and templates
henhuy Mar 9, 2026
ffeadc9
Add backwards option for organizations
henhuy Mar 9, 2026
f5901d7
Fix table permissions for organizations
henhuy Mar 9, 2026
d0909e8
Rename groups into organizations on table permission page
henhuy Mar 10, 2026
e1a3e79
Rename groups into organizations on table permission page
henhuy Mar 10, 2026
fe7ed1e
Add fields to organization model
henhuy Mar 10, 2026
1b5473f
Show image in organization form if present
henhuy Mar 10, 2026
94a0944
Add REUSE information
henhuy Mar 10, 2026
daec2eb
Add migration file
henhuy Mar 10, 2026
52534e8
Add changelog entry
henhuy Mar 10, 2026
952e7e5
ADD REUSE information to migration file
henhuy Mar 10, 2026
8d8b226
Fix tests for organizations in login app
henhuy Mar 10, 2026
8d7b67d
Rename `OrganizationMembership` to `Membership`, `OrganizationPermiss…
henhuy Mar 10, 2026
0c75adf
Add REUSE information to migrations
henhuy Mar 10, 2026
406863f
Fix table permissions for non-admin user
henhuy Mar 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -675,16 +675,6 @@ SPDX-FileCopyrightText = [
"2022 Christian Winger <https://github.com/wingechr> © Öko-Institut e.V.",
]

[[annotations]]
path = "login/templates/login/list_memberships.html"
precedence = "override"
SPDX-License-Identifier = "AGPL-3.0-or-later"
SPDX-FileCopyrightText = [
"2017 Martin Glauer <https://github.com/MGlauer> © Otto-von-Guericke-Universität Magdeburg",
"2019 Johann Wagner <https://github.com/johannwagner> © Otto-von-Guericke-Universität Magdeburg",
"2022 Christian Winger <https://github.com/wingechr> © Öko-Institut e.V.",
]

[[annotations]]
path = "login/templates/login/detach.html"
precedence = "override"
Expand Down
2 changes: 1 addition & 1 deletion api/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def test_views(self):
# "api:oeo-search", # only when USE_LOEP
# "api:oevkg-query", # only when USE_ONTOP

self.get("api:grpprop", logged_in=True)
self.get("api:groupprop", logged_in=True)
self.get("api:usrprop", logged_in=True)
self.get("api:list-framework-factsheets")
self.get("api:list-model-factsheets")
Expand Down
4 changes: 2 additions & 2 deletions api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
TableMovePublishAPIView,
TableRowsAPIView,
TableUnpublishAPIView,
grpprop_api_view,
groupprop_api_view,
oeo_search_api_view,
oevkg_query_api_view,
table_approx_row_count_view,
Expand Down Expand Up @@ -294,7 +294,7 @@
urlpatterns = [
path("v0/", include(urlpatterns_v0)),
path("usrprop/", usrprop_api_view, name="usrprop"),
path("grpprop/", grpprop_api_view, name="grpprop"),
path("groupprop/", groupprop_api_view, name="groupprop"),
path("oeo-search", oeo_search_api_view, name="oeo-search"),
path("oevkg-query", oevkg_query_api_view, name="oevkg-query"),
]
12 changes: 6 additions & 6 deletions api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -928,9 +928,9 @@ def usrprop_api_view(request: Request) -> JsonLikeResponse:

@never_cache
@api_exception
def grpprop_api_view(request: Request) -> JsonLikeResponse:
def groupprop_api_view(request: Request) -> JsonLikeResponse:
"""
Return all Groups where this user is a member that match
Return all groups where this user is a member that match
the current query. The query is input by the User.
"""
try:
Expand All @@ -946,18 +946,18 @@ def grpprop_api_view(request: Request) -> JsonLikeResponse:
groups = [g.group for g in user_groups]

# Assuming 'name' is the field you want to search against
similar_groups = (
login_models.Group.objects.annotate(
similar_organizations = (
login_models.Organization.objects.annotate(
similarity=TrigramSimilarity("name", query),
)
.filter(
similarity__gt=0.2, # Adjust the threshold as needed
id__in=[group.pk for group in groups],
id__in=[organization.pk for organization in groups],
)
.order_by("-similarity")[:5]
)

group_names = [group.name for group in similar_groups]
group_names = [group.name for group in similar_organizations]

return JsonResponse(group_names, safe=False)

Expand Down
4 changes: 2 additions & 2 deletions base/static/css/bootstrap.min.css

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions base/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from django.urls import URLPattern, URLResolver, get_resolver, reverse
from django.urls.resolvers import RegexPattern, RoutePattern

from login.models import GroupMembership, UserGroup
from login.models import Membership, Organization
from login.models import myuser as User
from oeplatform.settings import IS_TEST

Expand Down Expand Up @@ -78,13 +78,13 @@ def setUpClass(cls):
cls.user = User.objects.create_user( # type: ignore
name="test", email="test@test.test", affiliation="test"
)
cls.group = UserGroup.objects.create()
GroupMembership.objects.create(user=cls.user, group=cls.group)
cls.organization = Organization.objects.create()
Membership.objects.create(user=cls.user, group=cls.organization)

@classmethod
def tearDownClass(cls):
cls.user.delete()
cls.group.delete()
cls.organization.delete()
super(TestViewsTestCase, cls).tearDownClass()

def get(
Expand Down
2 changes: 1 addition & 1 deletion dataedit/templates/dataedit/table_permissions.html
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ <h4>User Permissions</h4>
$("#group_field").autocomplete({
source: function (request, response) {
$.ajax({
url: "{% url 'api:usrprop' %}",
url: "{% url 'api:groupprop' %}",
dataType: "json",
data: { name: request.term },
success: function (data) {
Expand Down
6 changes: 3 additions & 3 deletions dataedit/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -925,7 +925,7 @@ def __add_group(self, request: HttpRequest, table_obj: Table):
# Return an HTTP 400 Bad Request response
return HttpResponseBadRequest("Group name is required.")

group = get_object_or_404(login_models.UserGroup, name=group_name)
group = get_object_or_404(login_models.Group, name=group_name)

p, _ = login_models.GroupPermission.objects.get_or_create(
holder=group, table=table_obj
Expand All @@ -939,7 +939,7 @@ def __change_group(self, request: HttpRequest, table_obj: Table):
# Return an HTTP 400 Bad Request response
return HttpResponseBadRequest("Group id is required.")

group = get_object_or_404(login_models.UserGroup, id=group_id)
group = get_object_or_404(login_models.Group, id=group_id)

p = get_object_or_404(
login_models.GroupPermission, holder=group, table=table_obj
Expand All @@ -954,7 +954,7 @@ def __remove_group(self, request: HttpRequest, table_obj: Table):
# Return an HTTP 400 Bad Request response
return HttpResponseBadRequest("Group id is required.")

group = get_object_or_404(login_models.UserGroup, id=group_id)
group = get_object_or_404(login_models.Group, id=group_id)

p = get_object_or_404(
login_models.GroupPermission, holder=group, table=table_obj
Expand Down
4 changes: 4 additions & 0 deletions docs/installation/guides/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,10 @@ system like GitHub.
- copy the file `securitysettings.py.default` and rename it to
`securitysettings.py`

!!! danger

You must remove or fill placeholder in SOCIALACCOUNT_PROVIDERS in order to avoid an error due to empty provider ID when trying to login.

??? note "How to configure securitysettings.py"

The security settings provide information to django to connect to your databases, relevant for step 5, below. You can provide the access credentials directly in the script or import them using environment variables. For detailed instructions see section [3. of the manual database setup guide](./manual-db-setup.md#3-connect-database-to-the-django-project).
Expand Down
26 changes: 13 additions & 13 deletions login/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.contrib.auth.forms import PasswordChangeForm, UserChangeForm

from login.models import UserGroup
from login.models import Organization
from login.models import myuser as OepUser


Expand Down Expand Up @@ -86,32 +86,32 @@ def save(self, user):
user.send_activation_mail(reset_token=True)


class GroupForm(forms.ModelForm):
class OrganizationForm(forms.ModelForm):
class Meta:
model = UserGroup
fields = ("name", "description")
model = Organization
fields = ("name", "acronym", "description", "image", "homepage")


class GroupUserForm(forms.ModelForm):
class OrganizationUserForm(forms.ModelForm):
"""
A form for setting members of a group.
A form for setting members of an organization.
"""

class Meta:
model = OepUser
fields = ("groupmembers",)
fields = ("members",)

groupmembers = forms.ModelMultipleChoiceField(
members = forms.ModelMultipleChoiceField(
queryset=OepUser.objects,
widget=FilteredSelectMultiple("Members", is_stacked=False),
required=False,
)

def __init__(self, *args, **kwargs):
# group_id is the parameter passed from views.py
group_id = kwargs.pop("group_id")
super(GroupUserForm, self).__init__(*args, **kwargs)
if group_id != "":
self.fields["groupmembers"].initial = OepUser.objects.filter(
groups__id=group_id
organization_id = kwargs.pop("organization_id")
super(OrganizationUserForm, self).__init__(*args, **kwargs)
if organization_id != "":
self.fields["members"].initial = OepUser.objects.filter(
groups__id=organization_id
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
"""
SPDX-FileCopyrightText: 2026 Hendrik Huyskens <https://github.com/henhuy> © Reiner Lemoine Institut

SPDX-License-Identifier: AGPL-3.0-or-later
""" # noqa: 501

# Generated by Django 5.1.15 on 2026-03-05 13:17

import django.contrib.auth.models
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models

import login.models


class Migration(migrations.Migration):

dependencies = [
("auth", "0012_alter_user_first_name_max_length"),
("dataedit", "0045_alter_embargo_table"),
("login", "0023_alter_grouppermission_table_and_more"),
]

operations = [
migrations.CreateModel(
name="Organization",
fields=[
(
"group_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="auth.group",
),
),
("description", models.TextField(default="")),
("is_admin", models.BooleanField(default=False)),
],
bases=("auth.group", login.models.PermissionHolder),
managers=[
("objects", django.contrib.auth.models.GroupManager()),
],
),
migrations.AlterField(
model_name="groupmembership",
name="user",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="groups",
to=settings.AUTH_USER_MODEL,
),
),
migrations.CreateModel(
name="OrganizationMembership",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"level",
models.IntegerField(
choices=[
(0, "None"),
(4, "Invite"),
(8, "Remove"),
(12, "Admin"),
],
default=4,
),
),
(
"organization",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="memberships",
to="login.organization",
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="memberships",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"unique_together": {("user", "organization")},
},
),
migrations.CreateModel(
name="OrganizationPermission",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"level",
models.IntegerField(
choices=[
(0, "None"),
(4, "Write"),
(8, "Delete"),
(12, "Admin"),
],
default=0,
),
),
(
"holder",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="table_permissions",
to="login.organization",
),
),
(
"table",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="%(class)s_set",
to="dataedit.table",
),
),
],
options={
"abstract": False,
"unique_together": {("table", "holder")},
},
),
]
Loading
Loading