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

# -*- coding: utf-8 -*- 

"""Django page CMS ``managers``.""" 

from pages import settings 

from pages.utils import normalize_url, filter_link 

from pages.http import get_slug_and_relative_path 

 

from django.db import models, connection 

from django.db.models import Q 

from django.core.cache import cache 

from django.contrib.auth.models import User 

 

from datetime import datetime 

 

 

class PageManager(models.Manager): 

    """ 

    Page manager provide several filters to obtain pages :class:`QuerySet` 

    that respect the page attributes and project settings. 

    """ 

 

    def populate_pages(self, parent=None, child=5, depth=5): 

        """Create a population of :class:`Page <pages.models.Page>` 

        for testing purpose.""" 

        from pages.models import Content 

        author = User.objects.all()[0] 

        if depth == 0: 

            return 

        p = self.model(parent=parent, author=author, 

            status=self.model.PUBLISHED) 

        p.save() 

        Content(body='page-'+str(p.id), type='title', 

            language='en-us', page=p).save() 

        Content(body='page-'+str(p.id), type='slug', 

            language='en-us', page=p).save() 

        for child in range(1, child+1): 

            self.populate_pages(parent=p, child=child, depth=(depth-1)) 

 

    def on_site(self, site_id=None): 

        """Return a :class:`QuerySet` of pages that are published on the site 

        defined by the ``SITE_ID`` setting. 

 

        :param site_id: specify the id of the site object to filter with. 

        """ 

        if settings.PAGE_USE_SITE_ID: 

            if not site_id: 

                site_id = settings.SITE_ID 

            return self.filter(sites=site_id) 

        return self 

 

    def root(self): 

        """Return a :class:`QuerySet` of pages without parent.""" 

        return self.on_site().filter(parent__isnull=True) 

 

    def navigation(self): 

        """Creates a :class:`QuerySet` of the published root pages.""" 

        return self.on_site().filter( 

                status=self.model.PUBLISHED).filter(parent__isnull=True) 

 

    def hidden(self): 

        """Creates a :class:`QuerySet` of the hidden pages.""" 

        return self.on_site().filter(status=self.model.HIDDEN) 

 

    def filter_published(self, queryset): 

        """Filter the given pages :class:`QuerySet` to obtain only published 

        page.""" 

        if settings.PAGE_USE_SITE_ID: 

            queryset = queryset.filter(sites=settings.SITE_ID) 

 

        queryset = queryset.filter(status=self.model.PUBLISHED) 

 

        if settings.PAGE_SHOW_START_DATE: 

            queryset = queryset.filter(publication_date__lte=datetime.now()) 

 

        if settings.PAGE_SHOW_END_DATE: 

            queryset = queryset.filter( 

                Q(publication_end_date__gt=datetime.now()) | 

                Q(publication_end_date__isnull=True) 

            ) 

 

        return queryset 

 

    def published(self): 

        """Creates a :class:`QuerySet` of published 

        :class:`Page <pages.models.Page>`.""" 

        return self.filter_published(self) 

 

    def drafts(self): 

        """Creates a :class:`QuerySet` of drafts using the page's 

        :attr:`Page.publication_date`.""" 

        pub = self.on_site().filter(status=self.model.DRAFT) 

        if settings.PAGE_SHOW_START_DATE: 

            pub = pub.filter(publication_date__gte=datetime.now()) 

        return pub 

 

    def expired(self): 

        """Creates a :class:`QuerySet` of expired using the page's 

        :attr:`Page.publication_end_date`.""" 

        return self.on_site().filter( 

            publication_end_date__lte=datetime.now()) 

 

    def from_path(self, complete_path, lang, exclude_drafts=True): 

        """Return a :class:`Page <pages.models.Page>` according to 

        the page's path.""" 

        slug, path, lang = get_slug_and_relative_path(complete_path, lang) 

        page_ids = ContentManager().get_page_ids_by_slug(slug) 

        pages_list = self.on_site().filter(id__in=page_ids) 

        if exclude_drafts: 

            pages_list = pages_list.exclude(status=self.model.DRAFT) 

        current_page = None 

        if len(pages_list) == 1: 

            return pages_list[0] 

        # if more than one page is matching the slug, 

        # we need to use the full URL 

        if len(pages_list) > 1: 

            for page in pages_list: 

                if page.get_complete_slug(lang) == complete_path: 

                    return page 

        return None 

 

 

class ContentManager(models.Manager): 

    """:class:`Content <pages.models.Content>` manager methods""" 

 

    PAGE_CONTENT_DICT_KEY = "page_content_dict_%d_%s_%d" 

 

    def sanitize(self, content): 

        """Sanitize a string in order to avoid possible XSS using 

        ``html5lib``.""" 

        import html5lib 

        from html5lib import sanitizer 

        p = html5lib.HTMLParser(tokenizer=sanitizer.HTMLSanitizer) 

        # TODO: that's a bit of a hack there 

        # we need to remove <html><head/><body>...</body></html> 

        return p.parse(content).toxml()[19:-14] 

 

    def set_or_create_content(self, page, language, ctype, body): 

        """Set or create a :class:`Content <pages.models.Content>` for a 

        particular page and language. 

 

        :param page: the concerned page object. 

        :param language: the wanted language. 

        :param ctype: the content type. 

        :param body: the content of the Content object. 

        """ 

        if settings.PAGE_SANITIZE_USER_INPUT: 

            body = self.sanitize(body) 

        try: 

            content = self.filter(page=page, language=language, 

                                  type=ctype).latest('creation_date') 

            content.body = body 

        except self.model.DoesNotExist: 

            content = self.model(page=page, language=language, body=body, 

                                 type=ctype) 

        content.save() 

        return content 

 

    def create_content_if_changed(self, page, language, ctype, body): 

        """Create a :class:`Content <pages.models.Content>` for a particular 

        page and language only if the content has changed from the last 

        time. 

 

        :param page: the concerned page object. 

        :param language: the wanted language. 

        :param ctype: the content type. 

        :param body: the content of the Content object. 

        """ 

        if settings.PAGE_SANITIZE_USER_INPUT: 

            body = self.sanitize(body) 

        try: 

            content = self.filter(page=page, language=language, 

                                  type=ctype).latest('creation_date') 

            if content.body == body: 

                return content 

        except self.model.DoesNotExist: 

            pass 

        content = self.create(page=page, language=language, body=body, 

                type=ctype) 

 

    def get_content(self, page, language, ctype, language_fallback=False): 

        """Gets the latest :class:`Content <pages.models.Content>` 

        for a particular page and language. Falls back to another 

        language if wanted. 

 

        :param page: the concerned page object. 

        :param language: the wanted language. 

        :param ctype: the content type. 

        :param language_fallback: fallback to another language if ``True``. 

        """ 

        if not language: 

            language = settings.PAGE_DEFAULT_LANGUAGE 

 

        frozen = int(bool(page.freeze_date)) 

        key = self.PAGE_CONTENT_DICT_KEY % (page.id, ctype, frozen) 

 

        if page._content_dict is None: 

            page._content_dict = dict() 

        if page._content_dict.get(key, None): 

            content_dict = page._content_dict.get(key) 

        else: 

            content_dict = cache.get(key) 

 

        # fill a dict object for each language 

        if not content_dict: 

            content_dict = {} 

            for lang in settings.PAGE_LANGUAGES: 

                params = { 

                    'language':lang[0], 

                    'type':ctype, 

                    'page':page 

                } 

                if page.freeze_date: 

                    params['creation_date__lte'] = page.freeze_date 

                # using the same variable name "language" introduce nasty bugs. 

                lang = lang[0] 

                try: 

                    content = self.filter(**params).latest() 

                    content_dict[lang] = content.body 

                except self.model.DoesNotExist: 

                    content_dict[lang] = '' 

            page._content_dict[key] = content_dict 

            cache.set(key, content_dict) 

 

        if language in content_dict and content_dict[language]: 

            return filter_link(content_dict[language], page, language, ctype) 

 

        if language_fallback: 

            for lang in settings.PAGE_LANGUAGES: 

                if lang[0] in content_dict and content_dict[lang[0]]: 

                    return filter_link(content_dict[lang[0]], page, lang[0], 

                        ctype) 

        return '' 

 

    def get_content_slug_by_slug(self, slug): 

        """Returns the latest :class:`Content <pages.models.Content>` 

        slug object that match the given slug for the current site domain. 

 

        :param slug: the wanted slug. 

        """ 

        content = self.filter(type='slug', body=slug) 

        if settings.PAGE_USE_SITE_ID: 

            content = content.filter(page__sites__id=settings.SITE_ID) 

        try: 

           content = content.latest('creation_date') 

        except self.model.DoesNotExist: 

            return None 

        else: 

            return content 

 

    def get_page_ids_by_slug(self, slug): 

        """Return all page's id matching the given slug. 

 

        :param slug: the wanted slug. 

        """ 

        sql = '''SELECT pages_content.page_id, 

            MAX(pages_content.creation_date) 

            FROM pages_content WHERE (pages_content.type = %s 

            AND pages_content.body =%s) 

            GROUP BY pages_content.page_id''' 

 

        cursor = connection.cursor() 

        cursor.execute(sql, ('slug', slug, )) 

        return [c[0] for c in cursor.fetchall()] 

 

 

class PageAliasManager(models.Manager): 

    """:class:`PageAlias <pages.models.PageAlias>` manager.""" 

 

    def from_path(self, request, path, lang): 

        """ 

        Resolve a request to an alias. returns a 

        :class:`PageAlias <pages.models.PageAlias>` if the url matches 

        no page at all. The aliasing system supports plain 

        aliases (``/foo/bar``) as well as aliases containing GET parameters 

        (like ``index.php?page=foo``). 

 

        :param request: the request object 

        :param path: the complete path to the page 

        :param lang: not used 

        """ 

        from pages.models import PageAlias 

 

        url = normalize_url(path) 

        # §1: try with complete query string 

        query = request.META.get('QUERY_STRING') 

        if query: 

            url = url + '?' + query 

        try: 

            alias = PageAlias.objects.get(url=url) 

            return alias 

        except PageAlias.DoesNotExist: 

            pass 

        # §2: try with path only 

        url = normalize_url(path) 

        try: 

            alias = PageAlias.objects.get(url=url) 

            return alias 

        except PageAlias.DoesNotExist: 

            pass 

        # §3: not alias found, we give up 

        return None