-
Notifications
You must be signed in to change notification settings - Fork 238
Expand file tree
/
Copy pathdatepublisher.py
More file actions
156 lines (125 loc) · 5.73 KB
/
datepublisher.py
File metadata and controls
156 lines (125 loc) · 5.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
"""
Allows setting a date range for when the page is active. Modifies the active()
manager method so that only pages inside the given range are used in the
default views and the template tags.
Depends on the page class having a "active_filters" list that will be used by
the page's manager to determine which entries are to be considered active.
"""
# ------------------------------------------------------------------------
from __future__ import absolute_import, unicode_literals
from datetime import datetime
from pytz.exceptions import AmbiguousTimeError
from django.db import models
from django.db.models import Q
from django.utils import timezone
from django.utils.cache import patch_response_headers
from django.utils.html import mark_safe
from django.utils.translation import ugettext_lazy as _
from feincms import extensions
# ------------------------------------------------------------------------
def format_date(d, if_none=''):
"""
Format a date in a nice human readable way: Omit the year if it's the
current year. Also return a default value if no date is passed in.
"""
if d is None:
return if_none
now = timezone.now()
fmt = (d.year == now.year) and '%d.%m' or '%d.%m.%Y'
return d.strftime(fmt)
def latest_children(self):
return self.get_children().order_by('-publication_date')
# ------------------------------------------------------------------------
def granular_now(n=None, default_tz=None):
"""
A datetime.now look-alike that returns times rounded to a five minute
boundary. This helps the backend database to optimize/reuse/cache its
queries by not creating a brand new query each time.
Also useful if you are using johnny-cache or a similar queryset cache.
"""
if n is None:
n = timezone.now()
if default_tz is None:
default_tz = n.tzinfo
# Django 1.9:
# The correct way to resolve the AmbiguousTimeError every dst
# transition is... the is_dst parameter appeared with 1.9
# make_aware(some_datetime, get_current_timezone(), is_dst=True)
rounded_minute = (n.minute // 5) * 5
d = datetime(n.year, n.month, n.day, n.hour, rounded_minute)
try:
retval = timezone.make_aware(d, default_tz)
except AmbiguousTimeError:
try:
retval = timezone.make_aware(d, default_tz, is_dst=False)
except TypeError: # Pre-Django 1.9
retval = timezone.make_aware(
datetime(n.year, n.month, n.day, n.hour + 1, rounded_minute),
default_tz)
return retval
# ------------------------------------------------------------------------
def datepublisher_response_processor(page, request, response):
"""
This response processor is automatically added when the datepublisher
extension is registered. It sets the response headers to match with
the publication end date of the page so that upstream caches and
the django caching middleware know when to expunge the copy.
"""
expires = page.publication_end_date
if expires is not None:
delta = expires - timezone.now()
delta = int(delta.days * 86400 + delta.seconds)
patch_response_headers(response, delta)
# ------------------------------------------------------------------------
class Extension(extensions.Extension):
def handle_model(self):
self.model.add_to_class(
'publication_date',
models.DateTimeField(_('publication date'), default=granular_now))
self.model.add_to_class(
'publication_end_date',
models.DateTimeField(
_('publication end date'),
blank=True, null=True,
help_text=_(
'Leave empty if the entry should stay active forever.')))
self.model.add_to_class('latest_children', latest_children)
# Patch in rounding the pub and pub_end dates on save
orig_save = self.model.save
def granular_save(obj, *args, **kwargs):
if obj.publication_date:
obj.publication_date = granular_now(obj.publication_date)
if obj.publication_end_date:
obj.publication_end_date = granular_now(
obj.publication_end_date)
orig_save(obj, *args, **kwargs)
self.model.save = granular_save
# Append publication date active check
if hasattr(self.model._default_manager, 'add_to_active_filters'):
self.model._default_manager.add_to_active_filters(
lambda queryset: queryset.filter(
Q(publication_date__lte=granular_now()) &
(Q(publication_end_date__isnull=True) |
Q(publication_end_date__gt=granular_now()))),
key='datepublisher',
)
# Processor to patch up response headers for expiry date
self.model.register_response_processor(
datepublisher_response_processor)
def handle_modeladmin(self, modeladmin):
def datepublisher_admin(self, obj):
return mark_safe('%s – %s' % (
format_date(obj.publication_date),
format_date(obj.publication_end_date, '∞'),
))
datepublisher_admin.short_description = _('visible from - to')
modeladmin.__class__.datepublisher_admin = datepublisher_admin
try:
pos = modeladmin.list_display.index('is_visible_admin')
except ValueError:
pos = len(modeladmin.list_display)
modeladmin.list_display.insert(pos + 1, 'datepublisher_admin')
modeladmin.add_extension_options(_('Date-based publishing'), {
'fields': ['publication_date', 'publication_end_date'],
})
# ------------------------------------------------------------------------