Skip to content

Commit

Permalink
#240 Page views and Page context classes (#262)
Browse files Browse the repository at this point in the history
* #240  Process DB templates with new page view class

* #240  Create page context. Move base contexts to page module side

* #240  Rename module `db_views` to `display`

* #240  Add test for attribute access

* #240  Merge fix

* #240  Merge fix #2
  • Loading branch information
duker33 authored Feb 14, 2019
1 parent e37f7f8 commit f0a7334
Show file tree
Hide file tree
Showing 11 changed files with 124 additions and 36 deletions.
2 changes: 1 addition & 1 deletion catalog/newcontext/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
# @todo #207:15m Annotate methods of Context implementations.

from . import category, products, tags
from .context import Context, Contexts, ModelContext, Products, Tags
from .context import Context, ModelContext, Products, Tags
2 changes: 1 addition & 1 deletion catalog/newcontext/category.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from catalog.models import AbstractCategory
from catalog.newcontext.context import Context as BaseContext
from pages.newcontext import Context as BaseContext


class Context(BaseContext):
Expand Down
22 changes: 2 additions & 20 deletions catalog/newcontext/context.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import abc
import collections

from catalog import typing


class Context(abc.ABC):
from catalog import typing

@abc.abstractmethod
def context(self) -> typing.Dict[str, typing.Any]:
...
from pages.newcontext import Context


class ModelContext(abc.ABC):
Expand All @@ -27,19 +22,6 @@ def context(self) -> typing.Dict[str, typing.Any]:
Context.register(ModelContext)


class Contexts(Context):

# use context as list, not as args pack (`*context`)
# to move attention on homogeneous nature of args
def __init__(self, contexts: typing.List[Context]):
self.contexts = contexts or []

def context(self):
return dict(collections.ChainMap(
*[ctx.context() for ctx in self.contexts]
))


class Tags(ModelContext):

def context(self):
Expand Down
42 changes: 42 additions & 0 deletions pages/display.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""
Views processing db based templates.
This view are outside of MTV concept.
Responsible only for rendering given context data with db preserved text template.
"""
import typing

from pages import models


class Fields:
# Fields stored in DB. See class `pages.models.PageTemplate`
STORED = ['name', 'h1', 'keywords', 'description', 'title', 'seo_text']

def __init__(self, page_display: 'Page'):
self.page_display = page_display

def __getattr__(self, item):
if item in self.STORED:
return self.page_display.render(item)
else:
return super().__getattribute__(item)


class Page:
# @todo #240:30m Create usage doc for page view.

def __init__(self, page: models.Page, context: typing.Dict[str, typing.Any]):
"""
Pass context at ctor, but not render method,
because client code wants the same context for many different cases.
"""
self.page = page
self.context = context
self.fields = Fields(self)

def render(self, field: str):
return (
self.page.template.render_field(field, self.context)
if self.page.template
else getattr(self.page, field)
)
10 changes: 5 additions & 5 deletions pages/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ class Meta:
def __str__(self):
return self.name

def render(self, field, context):
return render_str(field, context)
def render_field(self, field: str, context: dict) -> str:
return render_str(getattr(self, field), context)


class PageQuerySet(mptt.querysets.TreeQuerySet):
Expand Down Expand Up @@ -211,9 +211,9 @@ def display_attribute(self, name):
if not self.template:
return getattr(self, name) or self.name

return self.template.render(
getattr(self.template, name),
self.get_template_render_context(),
return self.template.render_field(
field=name,
context=self.get_template_render_context(),
)

@property
Expand Down
3 changes: 3 additions & 0 deletions pages/newcontext/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from . import base, pages
from .base import Context, Contexts
from .pages import Page
23 changes: 23 additions & 0 deletions pages/newcontext/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import abc
import collections
import typing


class Context(abc.ABC):

@abc.abstractmethod
def context(self) -> typing.Dict[str, typing.Any]:
...


class Contexts(Context):

# use context as list, not as args pack (`*context`)
# to move attention on homogeneous nature of args
def __init__(self, contexts: typing.List[Context]):
self.contexts = contexts or []

def context(self):
return dict(collections.ChainMap(
*[ctx.context() for ctx in self.contexts]
))
12 changes: 12 additions & 0 deletions pages/newcontext/pages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from .base import Context


class Page(Context):

def __init__(self, page):
self.page = page

def context(self):
return {
'page': self.page,
}
7 changes: 5 additions & 2 deletions pages/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
from django.template import engines


def render_str(text, data):
# @todo #240:30m Create TextView class.
# Instead of render_str method.
# And inherit `pages.db_views.Page` from this class.
def render_str(template: str, context: dict):
django_engine = engines['django']
return django_engine.from_string(text).render(data)
return django_engine.from_string(template).render(context)


def save_custom_pages():
Expand Down
13 changes: 13 additions & 0 deletions tests/pages/test_display.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from django.test import TestCase

from pages import display


class TestFields(TestCase):
fixtures = []

def test_attribute_error(self):
# noinspection PyTypeChecker
page = display.Page(None, {})
with self.assertRaises(AttributeError):
_ = page.fields.bad_attr
24 changes: 17 additions & 7 deletions tests/pages/test_models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.test import TestCase

from pages import display
from pages.models import ModelPage, CustomPage, FlatPage, Page, PageTemplate

from tests.models import MockEntity, MockEntityWithSync
Expand Down Expand Up @@ -100,34 +101,43 @@ def test_slug_should_auto_generate(self):

self.assertTrue(page.slug)

# @todo #240:30m Improve DB templates (and views) tests.
# Move them to separated module.
# Rename theirs `test_display` prefix.
# Separate them on small pieces.
# Add test for `display.Page` with passing context.
def test_display_seo_fields(self):
page_with_custom_fields = Page.objects.create(
name='some page', slug='test', h1='test h1'
)
self.assertEqual(page_with_custom_fields.display_h1, 'test h1')
page_view = display.Page(page_with_custom_fields, {})
self.assertEqual(page_view.fields.h1, 'test h1')

custom_page_template = PageTemplate.objects.create(
name='test',
h1='{{ page.name }} - купить в СПб',
)

page_with_template = Page.objects.create(
page = Page.objects.create(
name='different page', template=custom_page_template
)

self.assertEqual(page_with_template.display_h1, 'different page - купить в СПб')
page_view = display.Page(page, {'page': page})

self.assertEqual(page_view.fields.h1, 'different page - купить в СПб')

def test_display_attribute_uses_template(self):
custom_page_template = PageTemplate.objects.create(
template = PageTemplate.objects.create(
name='test',
h1='{{ page.h1 }} - template',
)
page_with_template = Page.objects.create(
page = Page.objects.create(
name='different page',
h1='page h1',
template=custom_page_template,
template=template,
)
self.assertEqual(page_with_template.display_h1, 'page h1 - template')
page_view = display.Page(page, {'page': page})
self.assertEqual(page_view.fields.h1, 'page h1 - template')


class TestCustomPage(TestCase):
Expand Down

3 comments on commit f0a7334

@0pdd
Copy link
Collaborator

@0pdd 0pdd commented on f0a7334 Feb 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Puzzle 240-cb4ad3dc discovered in tests/pages/test_models.py and submitted as #267. Please, remember that the puzzle was not necessarily added in this particular commit. Maybe it was added earlier, but we discovered it only now.

@0pdd
Copy link
Collaborator

@0pdd 0pdd commented on f0a7334 Feb 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Puzzle 240-ac87cf5b discovered in pages/utils.py and submitted as #268. Please, remember that the puzzle was not necessarily added in this particular commit. Maybe it was added earlier, but we discovered it only now.

@0pdd
Copy link
Collaborator

@0pdd 0pdd commented on f0a7334 Feb 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Puzzle 240-41ca8b76 discovered in pages/display.py and submitted as #269. Please, remember that the puzzle was not necessarily added in this particular commit. Maybe it was added earlier, but we discovered it only now.

Please sign in to comment.