manager: list pages outside applications (#85927)
gitea/combo/pipeline/head This commit looks good Details

This commit is contained in:
Lauréline Guérin 2024-01-19 15:57:03 +01:00 committed by Lauréline Guérin
parent dd877aad7c
commit 97d0fd650e
5 changed files with 44 additions and 27 deletions

View File

@ -71,34 +71,23 @@ class Application(models.Model):
@classmethod
def populate_objects(cls, object_class, objects):
content_type = ContentType.objects.get_for_model(object_class)
elements = ApplicationElement.objects.filter(content_type=content_type)
elements = ApplicationElement.objects.filter(
content_type=content_type, application__visible=True
).prefetch_related('application')
elements_by_objects = collections.defaultdict(list)
for element in elements:
elements_by_objects[element.content_object].append(element)
applications_by_ids = {
a.pk: a for a in cls.objects.filter(pk__in=elements.values('application'), visible=True)
}
elements_by_objects[element.object_id].append(element)
for obj in objects:
applications = []
elements = elements_by_objects.get(obj) or []
for element in elements:
application = applications_by_ids.get(element.application_id)
if application:
applications.append(application)
applications = [element.application for element in elements_by_objects.get(obj.pk) or []]
obj._applications = sorted(applications, key=lambda a: a.name)
@classmethod
def load_for_object(cls, obj):
content_type = ContentType.objects.get_for_model(obj.__class__)
elements = ApplicationElement.objects.filter(content_type=content_type, object_id=obj.pk)
applications_by_ids = {
a.pk: a for a in cls.objects.filter(pk__in=elements.values('application'), visible=True)
}
applications = []
for element in elements:
application = applications_by_ids.get(element.application_id)
if application:
applications.append(application)
elements = ApplicationElement.objects.filter(
content_type=content_type, object_id=obj.pk, application__visible=True
).prefetch_related('application')
applications = [element.application for element in elements]
obj._applications = sorted(applications, key=lambda a: a.name)
def get_objects_for_object_class(self, object_class):
@ -106,6 +95,12 @@ class Application(models.Model):
elements = ApplicationElement.objects.filter(content_type=content_type, application=self)
return object_class.objects.filter(pk__in=elements.values('object_id'))
@classmethod
def get_orphan_objects_for_object_class(cls, object_class):
content_type = ContentType.objects.get_for_model(object_class)
elements = ApplicationElement.objects.filter(content_type=content_type, application__visible=True)
return object_class.objects.exclude(pk__in=elements.values('object_id'))
class ApplicationElement(models.Model):
application = models.ForeignKey(Application, on_delete=models.CASCADE)

View File

@ -11,6 +11,8 @@
{% endif %}
{{ application }}
</h2>
{% elif no_application %}
<h2>{% trans 'Pages outside applications' %}</h2>
{% else %}
<h2>{% trans 'Pages' %}</h2>
{% endif %}
@ -20,6 +22,8 @@
{{ block.super }}
{% if application %}
<a href="{% url 'combo-manager-homepage' %}?application={{ application.slug }}">{{ application }}</a>
{% elif no_application %}
<a href="{% url 'combo-manager-homepage' %}?no-application">{% trans "Pages outside applications" %}</a>
{% endif %}
{% endblock %}
@ -27,7 +31,7 @@
{% if object_list %}
{% if not application %}
{% if not application and not no_application %}
<p class="hint">
{% blocktrans %}
Use drag and drop with the ⣿ handles to reorder and change hierarchy of pages.
@ -38,10 +42,10 @@
<div class="objects-list" id="pages-list" data-page-order-url="{% url 'combo-manager-page-order' %}">
{% for page in object_list %}
<div class="page level-{{page.level}}{% if collapse_pages %} untoggled{% endif %}" data-page-id="{{page.id}}" data-level="{{page.level}}">
{% if user.is_superuser and not application %}<span class="handle"></span>{% endif %}
{% if user.is_superuser and not application and not no_application %}<span class="handle"></span>{% endif %}
<span class="group1">
<a href="{% url 'combo-manager-page-view' pk=page.id %}">
{% if not application %}
{% if not application and not no_application %}
{% for application in page.applications %}
{% if application.icon %}
{% thumbnail application.icon '16x16' format='PNG' as im %}
@ -80,7 +84,7 @@
{% endblock %}
{% block sidebar %}
{% if not application %}
{% if not application and not no_application %}
<aside id="sidebar">
{% if can_add_page or user.is_superuser %}
<h3>{% trans "Actions" %}</h3>
@ -112,6 +116,9 @@
{{ application.name }}
</a>
{% endfor %}
<a class="button button-paragraph" href="?no-application">
{% trans "Pages outside applications" %}
</a>
{% endif %}
</aside>
{% endif %}

View File

@ -115,15 +115,20 @@ class HomepageView(ListView):
def dispatch(self, request, *args, **kwargs):
self.application = None
self.no_application = False
if 'application' in self.request.GET:
self.application = get_object_or_404(
Application, slug=self.request.GET['application'], visible=True
)
elif 'no-application' in self.request.GET:
self.no_application = True
return super().dispatch(request, *args, **kwargs)
def get_queryset(self):
if self.application:
return self.application.get_objects_for_object_class(Page)
if self.no_application:
return Application.get_orphan_objects_for_object_class(Page)
return super().get_queryset()
def get_context_data(self, **kwargs):
@ -136,9 +141,10 @@ class HomepageView(ListView):
context['can_add_page'] = can_add_page(self.request.user)
if self.application:
context['application'] = self.application
else:
elif not self.no_application:
Application.populate_objects(Page, self.object_list)
context['applications'] = Application.select_for_object_class(Page)
context['no_application'] = self.no_application
return context

View File

@ -78,6 +78,7 @@ def test_pages(app, admin_user, application_with_icon, application_without_icon,
assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph img.application-icon')) == 1
else:
assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph img')) == 0
assert 'Pages outside applications' in resp
# check application view
resp = resp.click(application.name)
@ -93,6 +94,14 @@ def test_pages(app, admin_user, application_with_icon, application_without_icon,
assert resp.pyquery('#pages-list div.page:nth-child(2)').text() == 'Page 3'
assert len(resp.pyquery('#pages-list div.page img')) == 0
# check pages outside applications
resp = app.get('/manage/')
resp = resp.click('Pages outside applications')
assert resp.pyquery('h2').text() == 'Pages outside applications'
assert len(resp.pyquery('#pages-list div.page')) == 1
assert resp.pyquery('#pages-list div.page:nth-child(1)').attr['data-level'] == '0'
assert resp.pyquery('#pages-list div.page:nth-child(1)').text() == 'Page 1'
# check detail page
resp = app.get('/manage/pages/%s/' % page1.pk)
assert len(resp.pyquery('h3:contains("Applications")')) == 0

View File

@ -703,7 +703,7 @@ def test_edit_page_num_queries(settings, app, admin_user):
app.get('/manage/pages/%s/' % page.pk) # load once to populate caches
with CaptureQueriesContext(connection) as ctx:
app.get('/manage/pages/%s/' % page.pk)
assert len(ctx.captured_queries) == 43
assert len(ctx.captured_queries) == 42
def test_delete_page(app, admin_user):
@ -2668,7 +2668,7 @@ def test_page_versionning(app, admin_user):
resp = resp.click('restore', index=6)
with CaptureQueriesContext(connection) as ctx:
resp = resp.form.submit().follow()
assert len(ctx.captured_queries) == 150
assert len(ctx.captured_queries) == 149
resp2 = resp.click('See online')
assert resp2.text.index('Foobar1') < resp2.text.index('Foobar2') < resp2.text.index('Foobar3')