Repository: django-cms/django-cms

Issue:
Publication dates missing from page admin
The fields `publication_date` and `publication_end_date` are missing from the page admin. There is a form called `PublicationForm` in `cms.admin.forms` but it isn't used.



Discussion:
thanks for the heads up!
@digi604 : does dates deserve a separate form / admin method / special permission?


Resolving patch:
diff --git a/cms/admin/forms.py b/cms/admin/forms.py
--- a/cms/admin/forms.py
+++ b/cms/admin/forms.py
@@ -152,10 +152,26 @@ def clean_language(self):
         return language
 
 
-class PublicationForm(forms.ModelForm):
+class PublicationDatesForm(forms.ModelForm):
+    language = forms.ChoiceField(label=_("Language"), choices=get_language_tuple(),
+                                 help_text=_('The current language of the content fields.'))
+
+    def __init__(self, *args, **kwargs):
+        # Dates are not language dependent, so let's just fake the language to
+        # make the ModelAdmin happy
+        super(PublicationDatesForm, self).__init__(*args, **kwargs)
+        self.fields['language'].widget = HiddenInput()
+        self.fields['site'].widget = HiddenInput()
+        site_id = self.fields['site'].initial
+
+        languages = get_language_tuple(site_id)
+        self.fields['language'].choices = languages
+        if not self.fields['language'].initial:
+            self.fields['language'].initial = get_language()
+
     class Meta:
         model = Page
-        fields = ["publication_date", "publication_end_date"]
+        fields = ['site', 'publication_date', 'publication_end_date']
 
 
 class AdvancedSettingsForm(forms.ModelForm):
diff --git a/cms/admin/pageadmin.py b/cms/admin/pageadmin.py
--- a/cms/admin/pageadmin.py
+++ b/cms/admin/pageadmin.py
@@ -30,7 +30,8 @@
 from cms.utils.helpers import find_placeholder_relation
 from cms.admin.change_list import CMSChangeList
 from cms.admin.dialog.views import get_copy_dialog
-from cms.admin.forms import PageForm, AdvancedSettingsForm, PagePermissionForm
+from cms.admin.forms import (PageForm, AdvancedSettingsForm, PagePermissionForm,
+                             PublicationDatesForm)
 from cms.admin.permissionadmin import (PERMISSION_ADMIN_INLINES, PagePermissionInlineAdmin, ViewRestrictionInlineAdmin)
 from cms.admin.views import revert_plugins
 from cms.models import Page, Title, CMSPlugin, PagePermission, PageModeratorState, EmptyTitle, GlobalPagePermission, \
@@ -111,6 +112,7 @@ def get_urls(self):
             '',
             pat(r'^([0-9]+)/([a-z\-]+)/edit-field/$', self.edit_title_fields),
             pat(r'^([0-9]+)/advanced-settings/$', self.advanced),
+            pat(r'^([0-9]+)/dates/$', self.dates),
             pat(r'^([0-9]+)/permission-settings/$', self.permissions),
             pat(r'^([0-9]+)/delete-translation/$', self.delete_translation),
             pat(r'^([0-9]+)/move-page/$', self.move_page),
@@ -224,6 +226,8 @@ def get_form(self, request, obj=None, **kwargs):
             form = super(PageAdmin, self).get_form(request, obj, form=AdvancedSettingsForm, **kwargs)
         elif "permission" in request.path:
             form = super(PageAdmin, self).get_form(request, obj, form=PagePermissionForm, **kwargs)
+        elif "dates" in request.path:
+            form = super(PageAdmin, self).get_form(request, obj, form=PublicationDatesForm, **kwargs)
         else:
             form = super(PageAdmin, self).get_form(request, obj, form=PageForm, **kwargs)
         if 'language' in form.base_fields:
@@ -272,6 +276,9 @@ def advanced(self, request, object_id):
             raise PermissionDenied("No permission for editing advanced settings")
         return self.change_view(request, object_id, extra_context={'advanced_settings': True, 'title': _("Advanced Settings")})
 
+    def dates(self, request, object_id):
+        return self.change_view(request, object_id, extra_context={'publishing_dates': True, 'title': _("Publishing dates")})
+
     def permissions(self, request, object_id):
         page = get_object_or_404(Page, pk=object_id)
         if not page.has_change_permissions_permission(request):
@@ -392,7 +399,9 @@ def update_language_tab_context(self, request, obj, context=None):
         context.update({
             'language': language,
             'language_tabs': languages,
-            'show_language_tabs': len(list(languages)) > 1,
+            # Dates are not language dependent, thus we hide the language
+            # selection bar: the language is forced through the form class
+            'show_language_tabs': len(list(languages)) > 1 and not context.get('publishing_dates', False),
         })
         return context
 
diff --git a/cms/cms_toolbar.py b/cms/cms_toolbar.py
--- a/cms/cms_toolbar.py
+++ b/cms/cms_toolbar.py
@@ -273,6 +273,10 @@ def add_page_menu(self):
                     templates_menu.add_break(TEMPLATE_MENU_BREAK)
                 templates_menu.add_ajax_item(name, action=action, data={'template': path}, active=active)
         current_page_menu.add_break(PAGE_MENU_FIRST_BREAK)
+        # dates settings
+        dates_url = reverse('admin:cms_page_dates', args=(self.page.pk,))
+        current_page_menu.add_modal_item(_('Publishing dates'), url=dates_url, close_on_url=self.toolbar.URL_CHANGE,
+                                         disabled=(not self.toolbar.edit_mode))
         # advanced settings
         advanced_url = reverse('admin:cms_page_advanced', args=(self.page.pk,))
         advanced_disabled = not self.page.has_advanced_settings_permission(self.request) or not self.toolbar.edit_mode


Test patch:
diff --git a/cms/tests/admin.py b/cms/tests/admin.py
--- a/cms/tests/admin.py
+++ b/cms/tests/admin.py
@@ -1,6 +1,7 @@
 # -*- coding: utf-8 -*-
 from __future__ import with_statement
 import json
+import datetime
 from cms.admin.change_list import CMSChangeList
 from cms.admin.forms import PageForm, AdvancedSettingsForm
 from cms.admin.pageadmin import PageAdmin
@@ -28,6 +29,7 @@
 from django.http import (Http404, HttpResponseBadRequest, HttpResponseForbidden, HttpResponse)
 from django.utils.datastructures import MultiValueDictKeyError
 from django.utils.encoding import smart_str
+from django.utils import timezone
 
 
 class AdminTestsBase(CMSTestCase):
@@ -272,6 +274,58 @@ def test_delete_translation(self):
             response = self.client.post(URL_CMS_TRANSLATION_DELETE % page.pk, {'language': 'es-mx'})
             self.assertRedirects(response, URL_CMS_PAGE)
 
+    def test_change_dates(self):
+        admin, staff = self._get_guys()
+        page = create_page('test-page', 'nav_playground.html', 'en')
+        page.publish()
+        draft = page.get_draft_object()
+
+        with self.settings(USE_TZ=False):
+            original_date = draft.publication_date
+            original_end_date = draft.publication_end_date
+            new_date = timezone.now() - datetime.timedelta(days=1)
+            new_end_date = timezone.now() + datetime.timedelta(days=1)
+            url = reverse('admin:cms_page_dates', args=(draft.pk,))
+            with self.login_user_context(admin):
+                response = self.client.post(url, {
+                    'language': 'en',
+                    'site': draft.site.pk,
+                    'publication_date_0': new_date.date(),
+                    'publication_date_1': new_date.strftime("%H:%M:%S"),
+                    'publication_end_date_0': new_end_date.date(),
+                    'publication_end_date_1': new_end_date.strftime("%H:%M:%S"),
+                })
+                self.assertEqual(response.status_code, 302)
+                draft = Page.objects.get(pk=draft.pk)
+                self.assertNotEqual(draft.publication_date.timetuple(), original_date.timetuple())
+                self.assertEqual(draft.publication_date.timetuple(), new_date.timetuple())
+                self.assertEqual(draft.publication_end_date.timetuple(), new_end_date.timetuple())
+                if original_end_date:
+                    self.assertNotEqual(draft.publication_end_date.timetuple(), original_end_date.timetuple())
+
+        with self.settings(USE_TZ=True):
+            original_date = draft.publication_date
+            original_end_date = draft.publication_end_date
+            new_date = timezone.localtime(timezone.now()) - datetime.timedelta(days=1)
+            new_end_date = timezone.localtime(timezone.now()) + datetime.timedelta(days=1)
+            url = reverse('admin:cms_page_dates', args=(draft.pk,))
+            with self.login_user_context(admin):
+                response = self.client.post(url, {
+                    'language': 'en',
+                    'site': draft.site.pk,
+                    'publication_date_0': new_date.date(),
+                    'publication_date_1': new_date.strftime("%H:%M:%S"),
+                    'publication_end_date_0': new_end_date.date(),
+                    'publication_end_date_1': new_end_date.strftime("%H:%M:%S"),
+                })
+                self.assertEqual(response.status_code, 302)
+                draft = Page.objects.get(pk=draft.pk)
+                self.assertNotEqual(draft.publication_date.timetuple(), original_date.timetuple())
+                self.assertEqual(timezone.localtime(draft.publication_date).timetuple(), new_date.timetuple())
+                self.assertEqual(timezone.localtime(draft.publication_end_date).timetuple(), new_end_date.timetuple())
+                if original_end_date:
+                    self.assertNotEqual(draft.publication_end_date.timetuple(), original_end_date.timetuple())
+
     def test_change_template(self):
         admin, staff = self._get_guys()
         request = self.get_request('/admin/cms/page/1/', 'en')