Skip to content

Commit 801e889

Browse files
feat: Use grouper admin to resolve usability issues (#167)
* feat: grouper admin * Update to new GrouperModelAdmin * Update to match latest cms.admin.utils * Fix message, isort, flake8 and transifex integration * Remove unneeded svg * Update tests * Run tests on any branch * Update test dependencies * Update readme, add created by and modified fields in versioned change list * Fix: MySql annotation error * Flipped lines fixed * Fix: alias content admin redirect * Add some type hints * fix isort * Update changelog * add messages * fix isort * Fix missing icons * ci: auto fixes from pre-commit hooks for more information, see https://pre-commit.ci * fix flake8 * Fix static alias language detection bug * ci: auto fixes from pre-commit hooks for more information, see https://pre-commit.ci * Fix flake8 * ci: auto fixes from pre-commit hooks for more information, see https://pre-commit.ci * Adjust tests * fix pre-commit.ci * Remove get_versioning_state from admin list_display * Update changelog * Fix: get_langauge_from_request to get the language of toolbar object does not have one. * ci: auto fixes from pre-commit hooks for more information, see https://pre-commit.ci * Create alias content warning-free * ci: auto fixes from pre-commit hooks for more information, see https://pre-commit.ci * Django 4.2 compatibility * Refactor * ci: auto fixes from pre-commit hooks for more information, see https://pre-commit.ci * Fix lint errors * Remove unused admin filter and tests * Update translations * Fix cms config * ci: auto fixes from pre-commit hooks for more information, see https://pre-commit.ci * Update test requirements * Undo change to site selection logic * update messages, simplify alias plugin ui, update tests * fix flake8 * Fix: Typo in jquery selector * Update some comments --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 301c067 commit 801e889

File tree

69 files changed

+4370
-1735
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+4370
-1735
lines changed

.github/workflows/test.yml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ concurrency:
77
on:
88
pull_request:
99
push:
10-
branches:
11-
- master
1210

1311
jobs:
1412
unit-tests:
@@ -18,8 +16,6 @@ jobs:
1816
strategy:
1917
matrix:
2018
python-version:
21-
- 3.7
22-
- 3.8
2319
- 3.9
2420
- '3.10'
2521
- '3.11'

.tx/config

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[main]
22
host = https://www.transifex.com
33

4-
[o:divio:p:django-cms-alias:r:djangopo]
4+
[o:divio:p:django-cms-alias:r:djangocms-alias-locale-en-lc-messages-django-po--master]
55
file_filter = djangocms_alias/locale/<lang>/LC_MESSAGES/django.po
66
source_file = djangocms_alias/locale/en/LC_MESSAGES/django.po
77
type = PO

CHANGELOG.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ Unreleased
88
* Django 2.2 support removed
99
* Python 3.7 support removed
1010
* Changed test setup to run tox from github actions for consistency in testing
11+
* Use django CMS 4.1 GrouperModelAdmin utility (breaking change wrt. django CMS 4.0)
12+
* Admin menu entry show alias change list in the language of the currently shown object
13+
* Automatically create static aliases in the language needed (fixing #162)
1114

1215
1.11.0 (2022-10-14)
1316
===================

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ include LICENSE.txt
22
include README.rst
33
recursive-include djangocms_alias/static *
44
recursive-include djangocms_alias/templates *
5+
recursive-include djangocms_alias/locale *
56
recursive-exclude * *.pyc

README.rst

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,9 @@ django CMS Alias
66

77
|coverage| |python| |django| |djangocms4|
88

9-
django CMS Alias replicates and extends the alias function of django CMS version 3
10-
for django CMS version 4.
9+
django CMS Alias replicates and extends the alias function of django CMS version 3 for django CMS version 4.
1110

12-
An alias is a collection of plugins that is managed centrally. A reference
13-
can be added to any placeholder using the Alias plugin. Since the Alias plugin
14-
creates a reference any changes to the alias are immediately reflected at all
15-
places it is used.
11+
An alias is a collection of plugins that is managed centrally. A reference can be added to any placeholder using the Alias plugin. Since the Alias plugin creates a reference any changes to the alias are immediately reflected at all places it is used.
1612

1713
django CMS Alias supports versioning aliases by django CMS Versioning.
1814

@@ -53,13 +49,31 @@ to perform the application's database migrations.
5349
Usage
5450
=====
5551

52+
Static aliases
53+
==============
54+
55+
Static aliases appear in templates and replace static placeholders which were part of django CMS up to version 3.x.
56+
57+
Example::
58+
59+
{% load djangocms_alias_tags %}
60+
...
61+
<footer>
62+
{% static_alias 'footer' %}
63+
</footer>
64+
65+
Alias plugin
66+
============
67+
68+
Alternatively, aliases can be used with the Alias plugin. It allows to select which alias content is shown at the exact position the alias plugin is placed.
69+
5670
.. |coverage| image:: https://codecov.io/gh/django-cms/djangocms-alias/branch/master/graph/badge.svg
5771
:target: https://codecov.io/gh/django-cms/djangocms-alias
5872

5973
.. |python| image:: https://img.shields.io/badge/python-3.7+-blue.svg
6074
:target: https://pypi.org/project/djangocms-alias/
6175

62-
.. |django| image:: https://img.shields.io/badge/django-2.2,%203.2-blue.svg
76+
.. |django| image:: https://img.shields.io/badge/django-3.2--4.1-blue.svg
6377
:target: https://www.djangoproject.com/
6478

6579
.. |djangocms4| image:: https://img.shields.io/badge/django%20CMS-4-blue.svg

djangocms_alias/admin.py

Lines changed: 96 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,29 @@
1+
from django import forms
12
from django.contrib import admin
2-
from django.db.models.functions import Lower
3-
from django.template.loader import render_to_string
3+
from django.http import (
4+
Http404,
5+
HttpRequest,
6+
HttpResponse,
7+
HttpResponseRedirect,
8+
)
9+
from django.utils.safestring import mark_safe
410
from django.utils.translation import gettext_lazy as _
511

12+
from cms.admin.utils import GrouperModelAdmin
613
from cms.utils.permissions import get_model_permission_codename
714
from cms.utils.urlutils import admin_reverse
815

916
from parler.admin import TranslatableAdmin
1017

1118
from .cms_config import AliasCMSConfig
12-
from .constants import USAGE_ALIAS_URL_NAME
13-
from .filters import CategoryFilter, LanguageFilter, SiteFilter
14-
from .forms import AliasContentForm
19+
from .constants import (
20+
CHANGE_ALIAS_URL_NAME,
21+
DELETE_ALIAS_URL_NAME,
22+
LIST_ALIAS_URL_NAME,
23+
USAGE_ALIAS_URL_NAME,
24+
)
25+
from .filters import CategoryFilter, SiteFilter
26+
from .forms import AliasGrouperAdminForm
1527
from .models import Alias, AliasContent, Category
1628
from .urls import urlpatterns
1729
from .utils import (
@@ -27,18 +39,22 @@
2739
'AliasContentAdmin',
2840
]
2941

30-
alias_content_admin_classes = [admin.ModelAdmin]
31-
alias_content_admin_list_display = ('name', 'get_category',)
32-
alias_content_admin_list_filter = (SiteFilter, CategoryFilter, LanguageFilter,)
42+
alias_admin_classes = [GrouperModelAdmin]
43+
alias_admin_list_display = ['content__name', 'category', 'admin_list_actions']
3344
djangocms_versioning_enabled = AliasCMSConfig.djangocms_versioning_enabled
3445

3546
if djangocms_versioning_enabled:
36-
from djangocms_versioning.admin import ExtendedVersionAdminMixin
47+
from djangocms_versioning.admin import (
48+
ExtendedGrouperVersionAdminMixin,
49+
StateIndicatorMixin,
50+
)
51+
from djangocms_versioning.models import Version
3752

38-
from .filters import UnpublishedFilter
39-
alias_content_admin_classes.insert(0, ExtendedVersionAdminMixin)
40-
alias_content_admin_list_display = ('name', 'get_category',)
41-
alias_content_admin_list_filter = (SiteFilter, CategoryFilter, LanguageFilter, UnpublishedFilter)
53+
alias_admin_classes.insert(0, ExtendedGrouperVersionAdminMixin)
54+
alias_admin_classes.insert(0, StateIndicatorMixin)
55+
alias_admin_list_display.insert(-1, "get_author")
56+
alias_admin_list_display.insert(-1, "get_modified_date")
57+
alias_admin_list_display.insert(-1, "state_indicator")
4258

4359

4460
@admin.register(Category)
@@ -49,7 +65,7 @@ def save_model(self, request, obj, form, change):
4965
change = not obj._state.adding
5066
super().save_model(request, obj, form, change)
5167
if change:
52-
# Dont emit delete content because there is on_delete=PROTECT for
68+
# Don't emit delete content because there is on_delete=PROTECT for
5369
# category FK on alias
5470
emit_content_change(
5571
AliasContent._base_manager.filter(alias__in=obj.aliases.all()),
@@ -58,19 +74,31 @@ def save_model(self, request, obj, form, change):
5874

5975

6076
@admin.register(Alias)
61-
class AliasAdmin(admin.ModelAdmin):
62-
list_display = ['name', 'category']
63-
list_filter = ['site', 'category']
64-
fields = ('category', 'site')
77+
class AliasAdmin(*alias_admin_classes):
78+
list_display = alias_admin_list_display
79+
list_display_links = None
80+
list_filter = (SiteFilter, CategoryFilter,)
81+
fields = ('content__name', 'category', 'site', 'content__language')
6582
readonly_fields = ('static_code', )
83+
form = AliasGrouperAdminForm
84+
extra_grouping_fields = ("language",)
85+
EMPTY_CONTENT_VALUE = mark_safe(_("<i>Missing language</i>"))
6686

67-
def get_urls(self):
87+
def get_urls(self) -> list:
6888
return urlpatterns + super().get_urls()
6989

70-
def has_add_permission(self, request):
71-
return False
90+
def get_actions_list(self) -> list:
91+
"""Add alias usage list actions"""
92+
return super().get_actions_list() + [self._get_alias_usage_link]
93+
94+
def can_change_content(self, request: HttpRequest, content_obj: AliasContent) -> bool:
95+
"""Returns True if user can change content_obj"""
96+
if content_obj and is_versioning_enabled():
97+
version = Version.objects.get_for_content(content_obj)
98+
return version.check_modify.as_bool(request.user)
99+
return True
72100

73-
def has_delete_permission(self, request, obj=None):
101+
def has_delete_permission(self, request: HttpRequest, obj: Alias = None) -> bool:
74102
# Alias can be deleted by users who can add aliases,
75103
# if that alias is not referenced anywhere.
76104
if obj:
@@ -81,142 +109,67 @@ def has_delete_permission(self, request, obj=None):
81109
return request.user.is_superuser
82110
return False
83111

84-
def get_deleted_objects(self, objs, request):
112+
def save_model(self, request: HttpRequest, obj: Alias, form: forms.Form, change: bool) -> None:
113+
super().save_model(request, obj, form, change)
114+
115+
# Only emit content changes if Versioning is not installed because
116+
# Versioning emits its own signals for changes
117+
if not is_versioning_enabled():
118+
emit_content_change(
119+
AliasContent._base_manager.filter(alias=obj),
120+
sender=self.model,
121+
)
122+
123+
def get_deleted_objects(self, objs, request: HttpRequest) -> tuple:
85124
deleted_objects, model_count, perms_needed, protected = super().get_deleted_objects(objs, request)
86125
# This is bad and I should feel bad.
87126
if 'placeholder' in perms_needed:
88127
perms_needed.remove('placeholder')
89128
return deleted_objects, model_count, perms_needed, protected
90129

91-
def save_model(self, request, obj, form, change):
92-
super().save_model(request, obj, form, change)
93-
emit_content_change(
94-
AliasContent._base_manager.filter(alias=obj),
95-
sender=self.model,
96-
)
97-
98-
def delete_model(self, request, obj):
130+
def delete_model(self, request: HttpRequest, obj: Alias):
99131
super().delete_model(request, obj)
100-
emit_content_delete(
101-
AliasContent._base_manager.filter(alias=obj),
102-
sender=self.model,
103-
)
104132

105-
def has_module_permission(self, request):
106-
return False
133+
# Only emit content changes if Versioning is not installed because
134+
# Versioning emits it' own signals for changes
135+
if not is_versioning_enabled():
136+
emit_content_delete(
137+
AliasContent._base_manager.filter(alias=obj),
138+
sender=self.model,
139+
)
140+
141+
def _get_alias_usage_link(self, obj: Alias, request: HttpRequest, disabled: bool = False) -> str:
142+
url = admin_reverse(USAGE_ALIAS_URL_NAME, args=[obj.pk])
143+
return self.admin_action_button(url, "info", _("View usage"), disabled=disabled)
144+
145+
def _get_alias_delete_link(self, obj: Alias, request: HttpRequest) -> str:
146+
url = admin_reverse(DELETE_ALIAS_URL_NAME, args=[obj.pk])
147+
return self.admin_action_button(url, "bin", _("Delete Alias"),
148+
disabled=not self.has_delete_permission(request, obj))
107149

108150

109151
@admin.register(AliasContent)
110-
class AliasContentAdmin(*alias_content_admin_classes):
111-
form = AliasContentForm
112-
list_filter = alias_content_admin_list_filter
113-
list_display = alias_content_admin_list_display
152+
class AliasContentAdmin(admin.ModelAdmin):
114153
# Disable dropdown actions
115154
actions = None
116155
change_form_template = "admin/djangocms_alias/aliascontent/change_form.html"
117156

118-
def get_queryset(self, request):
119-
queryset = super().get_queryset(request)
120-
# Force the category set to Lower, to be able to sort the category in ascending/descending order
121-
queryset = queryset.annotate(alias_category_translations_ordered=Lower("alias__category__translations__name"))
122-
return queryset
123-
124-
# Add Alias category in the admin manager list and order field
125-
@admin.display(
126-
description=_('category'),
127-
ordering="alias_category_translations_ordered",
128-
)
129-
def get_category(self, obj):
130-
return obj.alias.category
131-
132-
def has_add_permission(self, request, obj=None):
133-
# FIXME: It is not currently possible to add an alias from the django admin changelist issue #97
134-
# https://github.com/django-cms/djangocms-alias/issues/97
135-
return False
136-
137-
def save_model(self, request, obj, form, change):
138-
super().save_model(request, obj, form, change)
157+
def changelist_view(self, request: HttpRequest, extra_context: dict = None) -> HttpResponse:
158+
"""Needed for the Alias Content Admin breadcrumbs"""
159+
return HttpResponseRedirect(admin_reverse(
160+
LIST_ALIAS_URL_NAME,
161+
))
139162

140-
# Only emit content changes if Versioning is not installed because
141-
# Versioning emits it's own signals for changes
142-
if not is_versioning_enabled():
143-
emit_content_change([obj], sender=self.model)
163+
def change_view(self, request: HttpRequest, object_id: int, form_url: str = '', extra_context: dict = None) -> HttpResponse:
164+
"""Needed for the Alias Content Admin breadcrumbs"""
165+
obj = self.model.admin_manager.filter(pk=object_id).first()
166+
if not obj:
167+
raise Http404()
168+
return HttpResponseRedirect(admin_reverse(
169+
CHANGE_ALIAS_URL_NAME, args=(obj.alias_id,)
170+
) + f"?language={obj.language}")
144171

145-
def delete_model(self, request, obj):
146-
super().delete_model(request, obj)
172+
def has_module_permission(self, request: HttpRequest) -> bool:
173+
"""Hides admin class in admin site overview"""
147174

148-
# Only emit content changes if Versioning is not installed because
149-
# Versioning emits it's own signals for changes
150-
if not is_versioning_enabled():
151-
emit_content_delete([obj], sender=self.model)
152-
153-
def get_list_actions(self):
154-
"""
155-
Collect rendered actions from implemented methods and return as list
156-
"""
157-
return [
158-
self._get_preview_link,
159-
self._get_edit_link,
160-
self._get_manage_versions_link,
161-
self._get_change_alias_settings_link,
162-
self._get_rename_alias_link,
163-
self._get_alias_usage_link,
164-
]
165-
166-
def get_list_display_links(self, request, list_display):
167-
"""
168-
Remove the linked text when versioning is enabled, because versioning adds actions
169-
"""
170-
if is_versioning_enabled():
171-
self.list_display_links = None
172-
return super().get_list_display_links(request, list_display)
173-
174-
def _get_rename_alias_link(self, obj, request, disabled=False):
175-
url = admin_reverse('{}_{}_change'.format(
176-
obj._meta.app_label, obj._meta.model_name), args=(obj.pk,)
177-
)
178-
return render_to_string(
179-
"admin/djangocms_alias/icons/rename_alias.html",
180-
{"url": url, "disabled": disabled},
181-
)
182-
183-
def _get_alias_usage_link(self, obj, request, disabled=False):
184-
url = admin_reverse(USAGE_ALIAS_URL_NAME, args=[obj.alias.pk])
185-
return render_to_string(
186-
"admin/djangocms_alias/icons/view_usage.html",
187-
{"url": url, "disabled": disabled},
188-
)
189-
190-
def _get_change_alias_settings_link(self, obj, request, disabled=False):
191-
url = admin_reverse('{}_{}_change'.format(
192-
obj._meta.app_label, obj.alias._meta.model_name), args=(obj.alias.pk,)
193-
)
194-
return render_to_string(
195-
"admin/djangocms_alias/icons/change_alias_settings.html",
196-
{"url": url, "disabled": disabled},
197-
)
198-
199-
def _get_preview_link(self, obj, request, disabled=False):
200-
"""
201-
Return a user friendly button for previewing the content model
202-
:param obj: Instance of versioned content model
203-
:param request: The request to admin menu
204-
:param disabled: Should the link be marked disabled?
205-
:return: Preview icon template
206-
"""
207-
preview_url = obj.get_absolute_url()
208-
if not preview_url:
209-
disabled = True
210-
211-
return render_to_string(
212-
"djangocms_versioning/admin/icons/preview.html",
213-
{"url": preview_url, "disabled": disabled, "keepsideframe": False},
214-
)
215-
216-
def change_view(self, request, object_id, form_url='', extra_context=None):
217-
extra_context = extra_context or {}
218-
# Provide additional context to the changeform
219-
extra_context['is_versioning_enabled'] = is_versioning_enabled()
220-
return super().change_view(
221-
request, object_id, form_url, extra_context=extra_context,
222-
)
175+
return False

0 commit comments

Comments
 (0)