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

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

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

import itertools, re

from datetime import datetime

 

from django.db import models, connection

from django.contrib.sites.models import Site

from django.db.models import Q

from django.core.cache import cache

from django.contrib.auth.models import User

 

from pages import settings

from pages.utils import normalize_url, filter_link

from pages.http import get_slug_and_relative_path

 

class PageManager(models.Manager):

    """

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

    that respect the page settings.

    """

 

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

        """Create a population of pages 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):

            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.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 filter."""

        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."""

        from pages.models import Content, Page

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

        page_ids = Content.objects.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=Page.DRAFT)

        current_page = None

        if len(pages_list) == 1:

            return pages_list[0]

        # more than one page matching the slug, let's 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"""

 

    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``.

        """

        PAGE_CONTENT_DICT_KEY = "page_content_dict_%d_%s_%d"

        if not language:

            language = settings.PAGE_DEFAULT_LANGUAGE

 

        frozen = int(bool(page.freeze_date))

        content_dict = cache.get(PAGE_CONTENT_DICT_KEY % (page.id, ctype, frozen))

 

        # 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

                language=lang[0]

                try:

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

                    content_dict[language] = content.body

                except self.model.DoesNotExist:

                    content_dict[language] = ''

            cache.set(PAGE_CONTENT_DICT_KEY % (page.id, ctype, frozen),

                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 PagePermissionManager(models.Manager):

    """Hierachic page permission manager."""

 

    def get_page_id_list(self, user):

        """Give a list of :class:`Page <pages.models.Page>` ids where the

        user has rights or the string

        "All" if the user has all rights.

 

        :param user: the interested user.

        """

        if user.is_superuser:

            return 'All'

        id_list = []

        for perm in self.filter(user=user):

            if perm.type == 0:

                return "All"

            if perm.page.id not in id_list:

                id_list.append(perm.page.id)

            if perm.type == 2:

                for page in perm.page.get_descendants():

                    if page.id not in id_list:

                        id_list.append(page.id)

        return id_list

 

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