/
managers.py
495 lines (418 loc) · 19.1 KB
/
managers.py
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
# -*- coding: utf-8 -*-
from cms.cache.permissions import get_permission_cache, set_permission_cache
from cms.exceptions import NoPermissionsException
from cms.models.query import PageQuerySet
from cms.publisher import PublisherManager
from cms.utils.i18n import get_fallback_languages
from django.conf import settings
from django.contrib.sites.models import Site
from django.db import models
from django.db.models import Q
class PageManager(PublisherManager):
"""Use draft() and public() methods for accessing the corresponding
instances.
"""
def get_query_set(self):
"""Change standard model queryset to our own.
"""
return PageQuerySet(self.model)
def drafts(self):
return super(PageManager, self).drafts().exclude(
publisher_state=self.model.PUBLISHER_STATE_DELETE
)
def public(self):
return super(PageManager, self).public().exclude(
publisher_state=self.model.PUBLISHER_STATE_DELETE
)
# !IMPORTANT: following methods always return access to draft instances,
# take care on what you do one them. use Page.objects.public() for accessing
# the published page versions
# Just some of the queryset methods are implemented here, access queryset
# for more getting more supporting methods.
# TODO: check which from following methods are really required to be on
# manager, maybe some of them can be just accessible over queryset...?
def on_site(self, site=None):
return self.get_query_set().on_site(site)
def root(self):
"""
Return a queryset with pages that don't have parents, a.k.a. root. For
current site - used in frontend
"""
return self.get_query_set().root()
def all_root(self):
"""
Return a queryset with pages that don't have parents, a.k.a. root. For
all sites - used in frontend
"""
return self.get_query_set().all_root()
def valid_targets(self, page_id, request, perms, page=None):
"""
Give valid targets to move a page into the tree
"""
return self.get_query_set().valid_targets(page_id, request, perms, page)
def published(self, site=None):
return self.get_query_set().published(site)
def expired(self):
return self.drafts().expired()
# - seems this is not used anymore...
# def get_pages_with_application(self, path, language):
# """Returns all pages containing application for current path, or
# any parrent. Returned list is sorted by path length, longer path first.
# """
# paths = levelize_path(path)
# q = Q()
# for path in paths:
# # build q for all the paths
# q |= Q(title_set__path=path, title_set__language=language)
# app_pages = self.published().filter(q & Q(title_set__application_urls__gt='')).distinct()
# # add proper ordering
# app_pages.query.order_by.extend(('LENGTH(`cms_title`.`path`) DESC',))
# return app_pages
def get_all_pages_with_application(self):
"""Returns all pages containing applications for all sites.
Doesn't cares about the application language.
"""
return self.get_query_set().filter(title_set__application_urls__gt='').distinct()
def get_home(self, site=None):
return self.get_query_set().get_home(site)
def search(self, q, language=None, current_site_only=True):
"""Simple search function
Plugins can define a 'search_fields' tuple similar to ModelAdmin classes
"""
from cms.plugin_pool import plugin_pool
qs = self.get_query_set()
if settings.CMS_MODERATOR:
qs = qs.public()
if current_site_only:
site = Site.objects.get_current()
qs = qs.filter(site=site)
qt = Q(title_set__title__icontains=q)
# find 'searchable' plugins and build query
qp = Q()
plugins = plugin_pool.get_all_plugins()
for plugin in plugins:
c = plugin.model
if hasattr(c, 'search_fields'):
for field in c.search_fields:
qp |= Q(**{'placeholders__cmsplugin__%s__%s__icontains' % \
(c.__name__.lower(), field):q})
if language:
qt &= Q(title_set__language=language)
qp &= Q(cmsplugin__language=language)
qs = qs.filter(qt | qp)
return qs.distinct()
class TitleManager(PublisherManager):
def get_title(self, page, language, language_fallback=False):
"""
Gets the latest content for a particular page and language. Falls back
to another language if wanted.
"""
try:
title = self.get(language=language, page=page)
return title
except self.model.DoesNotExist:
if language_fallback:
try:
titles = self.filter(page=page)
fallbacks = get_fallback_languages(language)
for l in fallbacks:
for title in titles:
if l == title.language:
return title
return None
except self.model.DoesNotExist:
pass
else:
raise
return None
def get_page_slug(self, slug, site=None):
"""
Returns the latest slug for the given slug and checks if it's available
on the current site.
"""
if not site:
site = Site.objects.get_current()
try:
titles = self.filter(
slug=slug,
page__site=site,
).select_related()#'page')
except self.model.DoesNotExist:
return None
else:
return titles
# created new public method to meet test case requirement and to get a list of titles for published pages
def public(self):
return self.get_query_set().filter(page__publisher_is_draft=False, page__published=True)
def drafts(self):
return self.get_query_set().filter(page__publisher_is_draft=True)
def set_or_create(self, request, page, form, language):
"""
set or create a title for a particular page and language
"""
base_fields = [
'slug',
'title',
'meta_description',
'meta_keywords',
'page_title',
'menu_title'
]
advanced_fields = [
'application_urls',
'redirect',
]
cleaned_data = form.cleaned_data
try:
obj = self.get(page=page, language=language)
except self.model.DoesNotExist:
data = {}
for name in base_fields:
if name in cleaned_data:
data[name] = cleaned_data[name]
data['page'] = page
data['language'] = language
if page.has_advanced_settings_permission(request):
overwrite_url = cleaned_data.get('overwrite_url', None)
if overwrite_url:
data['has_url_overwrite'] = True
data['path'] = overwrite_url
for field in advanced_fields:
value = cleaned_data.get(field, None)
if value:
data[field] = value
return self.create(**data)
for name in base_fields:
value = cleaned_data.get(name, None)
setattr(obj, name, value)
if page.has_advanced_settings_permission(request):
overwrite_url = cleaned_data.get('overwrite_url', None)
obj.has_url_overwrite = bool(overwrite_url)
obj.path = overwrite_url
for field in advanced_fields:
setattr(obj, field, cleaned_data.get(field, None))
obj.save()
return obj
################################################################################
# Permissions
################################################################################
class BasicPagePermissionManager(models.Manager):
"""Global page permission manager accessible under objects.
!IMPORTANT: take care, PagePermissionManager extends this manager
"""
def with_user(self, user):
"""Get all objects for given user, also takes look if user is in some
group.
"""
return self.filter(Q(user=user) | Q(group__user=user))
def with_can_change_permissions(self, user):
"""Set of objects on which user haves can_change_permissions. !But only
the ones on which is this assigned directly. For getting reall
permissions use page.permissions manager.
"""
return self.with_user(user).filter(can_change_permissions=True)
class PagePermissionManager(BasicPagePermissionManager):
"""Page permission manager accessible under objects.
"""
def subordinate_to_user(self, user):
"""Get all page permission objects on which user/group is lover in
hierarchy then given user and given user can change permissions on them.
!IMPORTANT, but exclude objects with given user, or any group containing
this user - he can't be able to change his own permissions, because if
he does, and removes some permissions from himself, he will not be able
to add them anymore.
Example:
A
/ \
user B,E
/ \
C,X D,Y
Gives permission nodes C,X,D,Y under user, so he can edit
permissions if he haves can_change_permission.
Example:
A,Y
/ \
user B,E,X
/ \
C,X D,Y
Gives permission nodes C,D under user, so he can edit, but not
anymore to X,Y, because this users are on the same level or higher
in page hierarchy. (but only if user have can_change_permission)
Example:
A
/ \
user B,E
/ \ \
C,X D,Y user
/ \
I J,A
User permissions can be assigned to multiple page nodes, so merge of
all of them is required. In this case user can see permissions for
users C,X,D,Y,I,J but not A, because A user in higher in hierarchy.
If permission object holds group, this permission object can be visible
to user only if all of the group members are lover in hierarchy. If any
of members is higher then given user, this entry must stay invisible.
If user is superuser, or haves global can_change_permission permissions,
show him everything.
Result of this is used in admin for page permissions inline.
"""
from cms.models import GlobalPagePermission, Page
if user.is_superuser or \
GlobalPagePermission.objects.with_can_change_permissions(user):
# everything for those guys
return self.all()
# get user level
from cms.utils.permissions import get_user_permission_level
try:
user_level = get_user_permission_level(user)
except NoPermissionsException:
return self.none()
# get current site
site = Site.objects.get_current()
# get all permissions
page_id_allow_list = Page.permissions.get_change_permissions_id_list(user,site)
# get permission set, but without objects targeting user, or any group
# in which he can be
qs = self.filter(
page__id__in=page_id_allow_list,
page__level__gte=user_level,
)
qs = qs.exclude(user=user).exclude(group__user=user)
return qs
def for_page(self, page):
"""Returns queryset containing all instances somehow connected to given
page. This includes permissions to page itself and permissions inherited
from higher pages.
NOTE: this returns just PagePermission instances, to get complete access
list merge return of this function with Global permissions.
"""
from cms.models import ACCESS_DESCENDANTS, ACCESS_CHILDREN,\
ACCESS_PAGE_AND_CHILDREN, ACCESS_PAGE_AND_DESCENDANTS
q = Q(page__tree_id=page.tree_id) & (
Q(page=page)
| (Q(page__level__lt=page.level) & (Q(grant_on=ACCESS_DESCENDANTS) | Q(grant_on=ACCESS_PAGE_AND_DESCENDANTS)))
| (Q(page__level=page.level - 1) & (Q(grant_on=ACCESS_CHILDREN) | Q(grant_on=ACCESS_PAGE_AND_CHILDREN)))
)
return self.filter(q).order_by('page__level')
class PagePermissionsPermissionManager(models.Manager):
"""Page permissions permission manager.
!IMPORTANT: this actually points to Page model, not to PagePermission.
Seems this will be better approach. Accessible under permissions.
Maybe this even shouldn't be a manager - it mixes different models together.
"""
# we will return this in case we have a superuser, or permissions are not
# enabled/configured in settings
GRANT_ALL = 'All'
def get_publish_id_list(self, user, site):
"""
Give a list of page where the user has publish rights or the string "All" if
the user has all rights.
"""
return self.__get_id_list(user, site, "can_publish")
def get_change_id_list(self, user, site):
"""
Give a list of page where the user has edit rights or the string "All" if
the user has all rights.
"""
return self.__get_id_list(user, site, "can_change")
def get_add_id_list(self, user, site):
"""
Give a list of page where the user has add page rights or the string
"All" if the user has all rights.
"""
return self.__get_id_list(user, site, "can_add")
def get_delete_id_list(self, user, site):
"""
Give a list of page where the user has delete rights or the string "All" if
the user has all rights.
"""
return self.__get_id_list(user, site, "can_delete")
def get_advanced_settings_id_list(self, user, site):
"""
Give a list of page where the user can change advanced settings or the
string "All" if the user has all rights.
"""
return self.__get_id_list(user, site, "can_change_advanced_settings")
def get_change_permissions_id_list(self, user, site):
"""Give a list of page where the user can change permissions.
"""
return self.__get_id_list(user, site, "can_change_permissions")
def get_move_page_id_list(self, user, site):
"""Give a list of pages which user can move.
"""
return self.__get_id_list(user, site, "can_move_page")
def get_moderate_id_list(self, user, site):
"""Give a list of pages which user can moderate. If moderation isn't
installed, nobody can moderate.
"""
if not settings.CMS_MODERATOR:
return []
return self.__get_id_list(user, site, "can_moderate")
def get_view_id_list(self, user, site):
"""Give a list of pages which user can view.
"""
return self.__get_id_list(user, site, "can_view")
'''
def get_change_list_id_list(self, user, site):
"""This is used just in admin now. Gives all ids where user haves can_edit
and can_add merged together.
There is for sure a better way how to do this over sql, need to be
optimized...
"""
can_change = self.get_change_id_list(user)
can_add = self.get_add_id_list(user)
if can_change is can_add:
# GRANT_ALL case
page_id_list = can_change
else:
permission_set = filter(lambda i: not i is PagePermissionsPermissionManager.GRANT_ALL, [can_change, can_add])
if len(permission_set) is 1:
page_id_list = permission_set[0]
else:
page_id_list = list(set(can_change).union(set(can_add)))
return page_id_list
'''
def __get_id_list(self, user, site, attr):
from cms.models import (GlobalPagePermission, PagePermission,
MASK_PAGE, MASK_CHILDREN, MASK_DESCENDANTS)
if attr != "can_view":
if not user.is_authenticated() or not user.is_staff:
return []
if user.is_superuser or not settings.CMS_PERMISSION:
# got superuser, or permissions aren't enabled? just return grant
# all mark
return PagePermissionsPermissionManager.GRANT_ALL
# read from cache if posssible
cached = get_permission_cache(user, attr)
if cached is not None:
return cached
# check global permissions
global_permissions = GlobalPagePermission.objects.with_user(user)
if global_permissions.filter(**{
attr: True, 'sites__in':[site]
}).exists():
# user or his group are allowed to do `attr` action
# !IMPORTANT: page permissions must not override global permissions
return PagePermissionsPermissionManager.GRANT_ALL
# for standard users without global permissions, get all pages for him or
# his group/s
qs = PagePermission.objects.with_user(user)
qs.order_by('page__tree_id', 'page__level', 'page__lft')
# default is denny...
page_id_allow_list = []
for permission in qs:
if getattr(permission, attr):
# can add is special - we are actually adding page under current page
if permission.grant_on & MASK_PAGE or attr is "can_add":
page_id_allow_list.append(permission.page.id)
if permission.grant_on & MASK_CHILDREN and not attr is "can_add":
page_id_allow_list.extend(permission.page.get_children().values_list('id', flat=True))
elif permission.grant_on & MASK_DESCENDANTS:
page_id_allow_list.extend(permission.page.get_descendants().values_list('id', flat=True))
# store value in cache
set_permission_cache(user, attr, page_id_allow_list)
return page_id_allow_list
class PageModeratorStateManager(models.Manager):
def get_delete_actions(self):
from cms.models import PageModeratorState
return self.filter(action=PageModeratorState.ACTION_DELETE)