Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [GAPS-26] - djangocms-moderation integration #13

Merged
merged 34 commits into from
Sep 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
020eb08
Initial working implementation
Aiky30 Sep 8, 2021
b9578e4
Fix admin date-filter stylesheet path.
Aiky30 Sep 8, 2021
dbc6c89
Added a form and controls to be able to add a content expiry record
Aiky30 Sep 9, 2021
fc8e291
Temporary solution for a pop up
Aiky30 Sep 10, 2021
fad3a0a
Removed the add and delete controls
Aiky30 Sep 10, 2021
d777a92
Fixed an issue where the admin get_queryset filter affected all queri…
Aiky30 Sep 11, 2021
06eed34
Added a configuration option to add admin fields vis the cms configur…
Aiky30 Sep 13, 2021
2ba05e2
Remove the default add button control for the content expiry link int…
Aiky30 Sep 14, 2021
0b8e665
Fix the change from moderations config and added internal code docs
Aiky30 Sep 15, 2021
b35272f
Ensure that empty expiry records can be handled ratehr than return None
Aiky30 Sep 16, 2021
f53a07a
Created a new test for the moderation depednancies and updated failin…
Aiky30 Sep 16, 2021
c055126
Enforce admin endpoint permissions for expiry records
Aiky30 Sep 16, 2021
e9bb530
Merge branch 'feature/moderation-integration' of github.com:Aiky30/dj…
Aiky30 Sep 16, 2021
8c38081
Removed incorrect view permission checks
Aiky30 Sep 16, 2021
e84a93d
Added a new setting to control the default filter shown in the admin …
Aiky30 Sep 16, 2021
586b8ac
Code cleanup
Aiky30 Sep 16, 2021
804bf6e
Clean flake8 errors and try new isort config
Aiky30 Sep 16, 2021
02db273
Resolve isort errors
Aiky30 Sep 16, 2021
5ec2abc
Amended isort failures, may need to update the rules
Aiky30 Sep 16, 2021
47108a5
Fixed a filter test that shouldn't have passed and made another one w…
Aiky30 Sep 20, 2021
65bf926
Fix the tests
Aiky30 Sep 20, 2021
71bedff
Fix the isort settings for known libraries
Aiky30 Sep 20, 2021
acb6c96
Fix isort errors
Aiky30 Sep 20, 2021
548eceb
Fix freezegun import isort error
Aiky30 Sep 20, 2021
409b84b
Pre - review changes / cleanup
Aiky30 Sep 20, 2021
87df0a9
Added the correct moderation url albeit still disabled
Aiky30 Sep 20, 2021
250bd01
Merge branch 'main' of github.com:EligaServices/djangocms-content-exp…
Aiky30 Sep 20, 2021
0f9aa82
Fix isort errors
Aiky30 Sep 20, 2021
9db9933
Make the expiry field consistent with the versioning changes
Aiky30 Sep 21, 2021
d996727
Unpinned dependant requirements to ensure code compatibility now and …
Aiky30 Sep 21, 2021
bd2cc6a
Cleanup and added constant for the admin expiry field label
Aiky30 Sep 21, 2021
07183c4
Don;t break the tests if the value of the constant has just been chan…
Aiky30 Sep 21, 2021
7f9dcbd
Fix isort error
Aiky30 Sep 21, 2021
f8b8b13
Fix typo in monkeypatch
Aiky30 Sep 21, 2021
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
26 changes: 6 additions & 20 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,13 @@ Run::
to perform the application's database migrations.


=====
Usage
=====

Configuration
=============

The default Content Expiry changelist is filtered by a date range which is 30 days by default. This can be changed by setting a value in days as an integer in settings.py::

CMS_CONTENT_EXPIRY_DEFAULT_RANGEFILTER_DELTA=60


Testing
Expand All @@ -41,20 +44,3 @@ To run all the tests the only thing you need to do is run

pip install -r tests/requirements.txt
python setup.py test


Documentation
=============

We maintain documentation under the ``docs`` folder using rst format.

To generate the HTML documentation you will need to install ``sphinx`` (``pip install sphinx``) and ``graphviz`` (as per
your operating system's package management system). You can then generate the docs using the following command:

Run::

cd docs/
make html

This should generate all html files from rst documents under `docs/_build` folder, which can be browsed.

40 changes: 21 additions & 19 deletions djangocms_content_expiry/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,41 @@
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import ugettext_lazy as _

import datetime
from rangefilter.filters import DateRangeFilter

from .filters import AuthorFilter, ContentTypeFilter, VersionStateFilter
from .filters import (
AuthorFilter,
ContentExpiryDateRangeFilter,
ContentTypeFilter,
VersionStateFilter,
)
from .forms import ContentExpiryForm
from .helpers import get_rangefilter_expires_default
from .models import ContentExpiry


@admin.register(ContentExpiry)
class ContentExpiryAdmin(admin.ModelAdmin):
list_display = ['title', 'content_type', 'expires', 'version_state', 'version_author']
# Disable automatically linking to the Expiry record
list_display_links = None
list_filter = (ContentTypeFilter, ('expires', DateRangeFilter), VersionStateFilter, AuthorFilter)
list_filter = (ContentTypeFilter, ('expires', ContentExpiryDateRangeFilter), VersionStateFilter, AuthorFilter)
form = ContentExpiryForm

class Media:
css = {
'all': ('css/date_filter.css',)
'all': ('djangocms_content_expiry/css/date_filter.css',)
}

def get_queryset(self, request):
queryset = super().get_queryset(request)
def has_add_permission(self, *args, **kwargs):
# Entries are added automatically
return False

if 'expires__range' not in request.path:
default_gte, default_lte = self.get_rangefilter_expires_default(request)
queryset = queryset.filter(expires__range=(default_gte, default_lte))
return queryset
def has_delete_permission(self, *args, **kwargs):
# Deletion should never be possible, the only way that a
# content expiry record could be deleted is via versioning.
return False

def get_rangefilter_expires_default(self, request):
start_date = datetime.datetime.now() - datetime.timedelta(30)
end_date = datetime.datetime.now()
return start_date, end_date
def get_rangefilter_expires_default(self, *args, **kwargs):
return get_rangefilter_expires_default()

def get_rangefilter_expires_title(self, request, field_path):
def get_rangefilter_expires_title(self, *args, **kwargs):
return _("By Expiry Date Range")

def title(self, obj):
Expand Down
1 change: 1 addition & 0 deletions djangocms_content_expiry/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ class DjangocmsContentExpiryConfig(AppConfig):
def ready(self):
from .handlers import create_content_expiry
from .monkeypatch import admin as monkeypatch_admin # noqa: F401

signals.post_version_operation.connect(create_content_expiry)
59 changes: 59 additions & 0 deletions djangocms_content_expiry/cms_config.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,68 @@
from django.conf import settings
from django.template.loader import render_to_string
from django.urls import reverse
from django.utils.html import format_html

from cms.app_base import CMSAppConfig

from .constants import CONTENT_EXPIRY_EXPIRE_FIELD_LABEL


def get_moderation_content_expiry_link(obj):
"""
Return a user friendly button for viewing content expiry in the
actions section of the Moderation Request Admin Changelist
in djangocms-moderation.
:param obj: A Moderation Request object supplied from the admin view table row
:return: A link to the expiry record if one exists
"""
version = obj.moderation_request.version

# If a content expiry record exists we can go to it
if hasattr(version, "contentexpiry"):
view_endpoint = format_html(
"{}?collection__id__exact={}&_popup=1",
reverse("admin:djangocms_content_expiry_contentexpiry_change", args=[version.contentexpiry.pk]),
obj.pk,
)
return render_to_string(
"djangocms_content_expiry/calendar_icon.html", {"url": view_endpoint, "field_id": f"contentexpiry_{obj.pk}"}
)
return ""


def get_expiry_date(obj):
"""
A custom field to show the expiry date in the
Moderation Request Admin Changelist in djangocms-moderation.
:param obj: A Moderation Request object supplied from the admin view table row
:return: The expiry date from the matching moderation request object
"""
version = obj.moderation_request.version

if hasattr(version, "contentexpiry"):
return version.contentexpiry.expires


get_expiry_date.short_description = CONTENT_EXPIRY_EXPIRE_FIELD_LABEL


class ContentExpiryAppConfig(CMSAppConfig):
# Enable moderation to be able to "configure it"
djangocms_moderation_enabled = True
moderated_models = []
moderation_request_changelist_actions = [
get_moderation_content_expiry_link,
]
moderation_request_changelist_fields = [
get_expiry_date,
]
# Enable versioning because moderation is versioning dependant
djangocms_versioning_enabled = True
versioning = []

djangocms_content_expiry_enabled = getattr(
settings, "DJANGOCMS_CONTENT_EXPIRY_ENABLED", True
)
6 changes: 6 additions & 0 deletions djangocms_content_expiry/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.conf import settings


DEFAULT_RANGEFILTER_DELTA = getattr(
settings, "CMS_CONTENT_EXPIRY_DEFAULT_RANGEFILTER_DELTA", 30
)
4 changes: 4 additions & 0 deletions djangocms_content_expiry/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from django.utils.translation import ugettext_lazy as _


CONTENT_EXPIRY_EXPIRE_FIELD_LABEL = _("expiry date")
18 changes: 16 additions & 2 deletions djangocms_content_expiry/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@

from djangocms_versioning.constants import PUBLISHED, VERSION_STATES
from djangocms_versioning.versionables import _cms_extension
from rangefilter.filters import DateRangeFilter

from . import helpers
from .helpers import get_authors, get_rangefilter_expires_default


class SimpleListMultiselectFilter(admin.SimpleListFilter):
Expand Down Expand Up @@ -158,7 +159,7 @@ class AuthorFilter(admin.SimpleListFilter):
def lookups(self, request, model_admin):
from django.utils.encoding import force_text
options = []
for user in helpers.get_authors():
for user in get_authors():
options.append(
(force_text(user.pk), user.get_full_name() or user.get_username())
)
Expand All @@ -168,3 +169,16 @@ def queryset(self, request, queryset):
if self.value():
return queryset.filter(created_by=self.value()).distinct()
return queryset


class ContentExpiryDateRangeFilter(DateRangeFilter):
def queryset(self, request, queryset):
queryset = super().queryset(request, queryset)

# By default the widget should default to show a default duration and not all content
# expiry records
if not any('expires__range' in seed for seed in request.GET):
default_gte, default_lte = get_rangefilter_expires_default()
queryset = queryset.filter(expires__range=(default_gte, default_lte))

return queryset
24 changes: 24 additions & 0 deletions djangocms_content_expiry/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from django import forms
from django.contrib.admin import widgets
from django.contrib.admin.sites import site

from .models import ContentExpiry


class ForeignKeyReadOnlyWidget(widgets.ForeignKeyRawIdWidget):
"""
A Widget for displaying ForeignKeys in a read only interface rather than
in a <select> box.
"""
template_name = 'admin/widgets/foreign_key_read_only.html'


class ContentExpiryForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

version_field = ContentExpiry._meta.get_field('version').remote_field
self.fields['version'].widget = ForeignKeyReadOnlyWidget(version_field, site)

user_field = ContentExpiry._meta.get_field('created_by').remote_field
self.fields['created_by'].widget = ForeignKeyReadOnlyWidget(user_field, site)
14 changes: 14 additions & 0 deletions djangocms_content_expiry/helpers.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
from datetime import datetime, timedelta

from django.contrib.auth import get_user_model

from djangocms_content_expiry.conf import DEFAULT_RANGEFILTER_DELTA


def get_authors():
"""
Helper to return all authors created by content expiry
"""
User = get_user_model()
return User.objects.filter(contentexpiry__created_by__isnull=False).distinct()


def get_rangefilter_expires_default():
"""
Sets a default date range to help filter
Content Expiry records
"""
start_date = datetime.now() - timedelta(DEFAULT_RANGEFILTER_DELTA)
end_date = datetime.now()
return start_date, end_date
4 changes: 3 additions & 1 deletion djangocms_content_expiry/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

from djangocms_versioning.models import Version

from .constants import CONTENT_EXPIRY_EXPIRE_FIELD_LABEL


class ContentExpiry(models.Model):
created = models.DateTimeField(auto_now_add=True)
Expand All @@ -17,7 +19,7 @@ class ContentExpiry(models.Model):
on_delete=models.CASCADE,
verbose_name=_('version')
)
expires = models.DateTimeField(_("expiry date"))
expires = models.DateTimeField(CONTENT_EXPIRY_EXPIRE_FIELD_LABEL)

class Meta:
verbose_name = _("Content Expiry")
Expand Down
10 changes: 4 additions & 6 deletions djangocms_content_expiry/monkeypatch/admin.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from django.utils.translation import ugettext_lazy as _

from djangocms_versioning import admin

from djangocms_content_expiry.constants import CONTENT_EXPIRY_EXPIRE_FIELD_LABEL
from djangocms_content_expiry.models import ContentExpiry


Expand All @@ -12,19 +11,18 @@ def expires(self, obj):
return ""


expires.short_description = _('expire date')
expires.short_description = CONTENT_EXPIRY_EXPIRE_FIELD_LABEL
admin.VersionAdmin.expire = expires


def get_list_display(func):
"""
Register the locked field with the Versioning Admin
Register the expires field with the Versioning Admin
"""

def inner(self, request):
list_display = func(self, request)
created_by_index = list_display.index('created_by')
return list_display[:created_by_index] + (_('expire'),) + list_display[created_by_index:]
return list_display[:created_by_index] + ('expire',) + list_display[created_by_index:]

return inner

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<input type="hidden" name="{{ widget.name }}"{% if widget.value != None %} value="{{ widget.value|stringformat:'s' }}"{% endif %}>
{{ link_label }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{% load static i18n %}
<a class="btn cms-moderation-action-btn js-moderation-action related-widget-wrapper-link" id="change_content_expiry_id_{{ field_id }}" href="{{ url }}" title="{% trans 'View Expiry' %}"><span class="svg-juxtaposed-font"><img src="{% static 'djangocms_content_expiry/svg/calendar.svg' %}" /></span></a>
3 changes: 2 additions & 1 deletion djangocms_content_expiry/test_utils/polls/cms_config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from collections import OrderedDict

from cms.app_base import CMSAppConfig
from cms.utils.i18n import get_language_tuple

from collections import OrderedDict
from djangocms_versioning.datastructures import VersionableItem, default_copy

from .models import PollContent
Expand Down
5 changes: 3 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ combine_as_imports = true
include_trailing_comma = true
balanced_wrapping = true
skip = manage.py, migrations, .tox, node_modules
known_standard_library = mock
extra_standard_library = mock
known_django = django
known_cms = cms, menus
sections = FUTURE, STDLIB, DJANGO, CMS, THIRDPARTY, FIRSTPARTY, LIB, LOCALFOLDER
known_first_party = djangocms_content_expiry
sections = FUTURE, STDLIB, DJANGO, CMS, THIRDPARTY, FIRSTPARTY, LOCALFOLDER

[coverage:run]
branch = True
Expand Down
2 changes: 2 additions & 0 deletions test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
"djangocms_content_expiry",
"djangocms_content_expiry.test_utils.polls",
"djangocms_versioning",
"djangocms_moderation",
"rangefilter"
],
"MIGRATION_MODULES": {
"auth": None,
"cms": None,
"menus": None,
"djangocms_versioning": None,
"djangocms_moderation": None,
"djangocms_content_expiry": None,
},
"CMS_PERMISSION": True,
Expand Down
11 changes: 6 additions & 5 deletions tests/requirements/requirements_base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ django-treebeard>=4.3
argparse
dj-database-url
djangocms-admin-style>=1.5
django-admin-rangefilter
django-sekizai>=0.7
django-classy-tags>=0.7.2
iptools
Expand All @@ -16,10 +17,10 @@ django-app-helper
mock>=2.0.0
django-formtools
factory-boy
# Custom date range filter
django-admin-rangefilter
freezegun

# Unreleased django-cms 4.0 compatible packages
https://github.com/divio/django-cms/tarball/66c70394c9e144281a0b47d93e3784d06318acf9#egg=django-cms
https://github.com/divio/djangocms-text-ckeditor/archive/support/4.0.x.zip
https://github.com/divio/djangocms-versioning/tarball/492d01c974e40b007b71e2a7e961a9019a0d5ac4#egg=djangocms-versioning
https://github.com/divio/django-cms/tarball/release/4.0.x#egg=django-cms
https://github.com/divio/djangocms-text-ckeditor/tarball/support/4.0.x#egg=djangocms-text-ckeditor
https://github.com/divio/djangocms-versioning/tarball/master#egg=djangocms-versioning
https://github.com/django-cms/djangocms-moderation/tarball/release/1.0.x#egg=djangocms-moderation
Loading