Skip to main content

No project description provided

Project description

Garpix Page

Convenient page structure with any context and template. It is suitable not only for a blog, but also for large sites with a complex presentation. Supports SEO.

Quickstart

Install with pip:

pip install garpix_page

Add the garpix_page and dependency packages to your INSTALLED_APPS:

# settings.py

INSTALLED_APPS = [
    'modeltranslation',
    'polymorphic_tree',
    'polymorphic',
    'mptt',
    # ... django.contrib.*
    'django.contrib.sites',
    'tabbed_admin',
    'garpix_page',
    # third-party and your apps
]

SITE_ID=1

LANGUAGE_CODE = 'en'
USE_DEFAULT_LANGUAGE_PREFIX = False

LANGUAGES = (
    ('en', 'English'),
)

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['templates'],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

MIDDLEWARE = [
    'django.middleware.locale.LocaleMiddleware'
]

Package not included migrations, set path to migration directory. Don't forget create this directory (app/migrations/garpix_page/) and place empty __init__.py:

app/migrations/
app/migrations/__init__.py  # empty file
app/migrations/garpix_page/__init__.py  # empty file

Add path to settings:

# settings.py

MIGRATION_MODULES = {
    'garpix_page': 'app.migrations.garpix_page',
}

Run make migrations:

python manage.py makemigrations

Migrate:

python manage.py migrate

Add celery settings path to settings.py:

GARPIXCMS_CELERY_SETTINGS = 'app.celery.app'

Now, you can create your models from BasePage and set template and context. See example below.

Important

Page (Model Page) - model, subclass from BasePage. You create it yourself. There must be at least 1 descendant from BasePage.

Context - includes object and request. It is a function that returns a dictionary from model instance. Values from the key dictionary can be used in the template.

Template - standard Django template.

Example

Urls:

# app/urls.py

from django.contrib import admin
from django.urls import path, re_path
from django.conf.urls.i18n import i18n_patterns
from garpix_page.views.page import PageView
from multiurl import ContinueResolving, multiurl
from django.http import Http404
from django.conf import settings
from garpix_page.views.index import IndexView

urlpatterns = [
    path('admin/', admin.site.urls),
]

urlpatterns += i18n_patterns(
    multiurl(
        path('', PageView.as_view()),
        re_path(r'^(?P<url>.*?)$', PageView.as_view(), name='page'),
        re_path(r'^(?P<url>.*?)/$', PageView.as_view(), name='page'),
        path('', IndexView.as_view()),
        catch=(Http404, ContinueResolving),
    ),
    prefix_default_language=settings.USE_DEFAULT_LANGUAGE_PREFIX,
)

Models:

# app/models/page.py

from django.db import models
from garpix_page.models import BasePage


class Page(BasePage):
    content = models.TextField(verbose_name='Content', blank=True, default='')

    template = 'pages/default.html'

    class Meta:
        verbose_name = "Page"
        verbose_name_plural = "Pages"
        ordering = ('-created_at',)


# app/models/category.py

from garpix_page.models import BasePage


class Category(BasePage):
    template = 'pages/category.html'

    def get_context(self, request=None, *args, **kwargs):
        context = super().get_context(request, *args, **kwargs)
        posts = Post.on_site.filter(is_active=True, parent=kwargs['object'])
        context.update({
            'posts': posts
        })
        return context

    class Meta:
        verbose_name = "Category"
        verbose_name_plural = "Categories"
        ordering = ('-created_at',)


# app/models/post.py

from django.db import models
from garpix_page.models import BasePage


class Post(BasePage):
    content = models.TextField(verbose_name='Content', blank=True, default='')

    template = 'pages/post.html'

    class Meta:
        verbose_name = "Post"
        verbose_name_plural = "Posts"
        ordering = ('-created_at',)

Admins:

# app/admin/__init__.py

from .page import PageAdmin
from .category import CategoryAdmin
from .post import PostAdmin


# app/admin/page.py

from ..models.page import Page
from django.contrib import admin
from garpix_page.admin import BasePageAdmin


@admin.register(Page)
class PageAdmin(BasePageAdmin):
    pass

# app/admin/category.py

from ..models.category import Category
from django.contrib import admin
from garpix_page.admin import BasePageAdmin


@admin.register(Category)
class CategoryAdmin(BasePageAdmin):
    pass

# app/admin/post.py

from ..models.post import Post
from django.contrib import admin
from garpix_page.admin import BasePageAdmin


@admin.register(Post)
class PostAdmin(BasePageAdmin):
    pass

Translations:

# app/translation/__init__.py

from .page import PageTranslationOptions
from .category import CategoryTranslationOptions
from .post import PostTranslationOptions

# app/translation/page.py

from modeltranslation.translator import TranslationOptions, register
from ..models import Page


@register(Page)
class PageTranslationOptions(TranslationOptions):
    fields = ('content',)


# app/translation/category.py

from modeltranslation.translator import TranslationOptions, register
from ..models import Category


@register(Category)
class CategoryTranslationOptions(TranslationOptions):
    fields = []

# app/translation/post.py

from modeltranslation.translator import TranslationOptions, register
from ..models import Post


@register(Post)
class PostTranslationOptions(TranslationOptions):
    fields = ('content',)

Templates:

# templates/base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    {% include 'garpix_page/seo.html' %}
</head>
<body>
{% include 'garpix_page/admin_toolbar.html' %}
<main>
    {% block content %}404{% endblock %}
    {% block components %}
        {% for component in components %}
        {{ component.template }}
        {% include component.template %}
        {% endfor %}
    {% endblock %}
</main>
</body>
</html>


# templates/pages/default.html

{% extends 'base.html' %}

{% block content %}
<h1>{{object.title}}</h1>
<div>
    {{object.content|safe}}
</div>
{% endblock %}



# templates/pages/category.html

{% extends 'base.html' %}

{% block content %}
<h1>{{object.title}}</h1>
{% for post in posts %}
    <div>
        <h3><a href="{{post.get_absolute_url}}">{{post.title}}</a></h3>
    </div>
{% endfor %}

{% endblock %}



# templates/pages/post.html

{% extends 'base.html' %}

{% block content %}
<h1>{{object.title}}</h1>
<div>
    {{object.content|safe}}
</div>
{% endblock %}

Now you can auth in admin panel and starting add pages.

If you need to use a serializer whose model is this page, use the get_serializer() method to avoid circular imports.

Page permissions

If you need to add login access to your model pages, add login_required static field to your model.

To add some user permissions to page, add permissions static field to your page model:

class Post(BasePage):
    content = models.TextField(verbose_name='Content', blank=True, default='')

    template = 'pages/post.html'

    login_required = True
    permissions = [IsAdminUser,]

    class Meta:
        verbose_name = "Post"
        verbose_name_plural = "Posts"
        ordering = ('-created_at',)

API

You can use garpix_page with SPA sites.

Add to settings API_URL parameter:

API_URL = 'api'

Add to urls.py this:

urlpatterns += [
    re_path(r'{}/page_models_list/$'.format(settings.API_URL), PageApiListView.as_view()),
    re_path(r'{}/page/(?P<slugs>.*)$'.format(settings.API_URL), PageApiView.as_view()),
]

And you can test it:

http://localhost:8000/api/page/ - home page (empty slug) http://localhost:8000/api/page/another_page - another page (slug) http://localhost:8000/api/page/kategoriya/post-1 - sub page (slug)

Example answer:

{
    "page_model": "Post",
    "init_state": {
        "object": {
            "id": 4,
            "title": "post 1",
            "title_en": "post 1",
            "is_active": true,
            "display_on_sitemap": true,
            "slug": "post-1",
            "created_at": "2021-06-21T19:39:49.749460Z",
            "updated_at": "2021-06-21T19:39:49.749488Z",
            "seo_title": "",
            "seo_title_en": null,
            "seo_keywords": "",
            "seo_keywords_en": null,
            "seo_description": "",
            "seo_description_en": "",
            "seo_author": "",
            "seo_author_en": null,
            "seo_og_type": "website",
            "seo_image": null,
            "lft": 2,
            "rght": 3,
            "tree_id": 3,
            "level": 1,
            "content": "example",
            "content_en": "example",
            "polymorphic_ctype": 11,
            "parent": 3,
            "sites": [
                1
            ]
        }
    }
}

Error contexts

Module consists of 3 reserved names for page models: Page404, Page403 and Page401. These names are used for responses when corresponding error statuses are caught. Example answer on not found error:

{
    "page_model": "Page404",
    "init_state": {
        "object": null,
        "global": {}
    }
}

Components

It is possible to compose a page from components. You can do this in the same way as creating pages.

Model

# app/models/components.py
from django.db import models

from garpix_page.models import BaseComponent

class TextComponent(BaseComponent):
    text = models.TextField(verbose_name='Текст')

    class Meta:
        verbose_name = 'Текстовый компонент'
        verbose_name_plural = 'Текстовые компоненты'

    def get_context_data(self, request):  # add overriding this method to customize component's context
        context = super().get_context_data(request)
        return context

Admin

# app/admin/components.py
from django.contrib import admin

from garpix_page.admin.components.base_component import BaseComponentAdmin
from app.models import TextComponent


@admin.register(TextComponent)
class TextComponentAdmin(BaseComponentAdmin):
    pass

Translations:

# app/translation/components.py

from modeltranslation.translator import TranslationOptions, register
from app.models import TextComponent


@register(TextComponent)
class TextComponentTranslationOptions(TranslationOptions):
    fields = ('text',)

BaseComponent has m2m field pages to specify on which pages the component should be displayed. Through table also has view_order field to specify the ordering of components at the page (ascending order). You can override get_contex_data method to add some info to component context.

Example answer with some components:

{
    "page_model": "Page",
    "init_state": {
        "object": {
            "id": 1,
            "title": "page",
            "title_en": "page",
            "is_active": true,
            "display_on_sitemap": true,
            "slug": "page",
            "created_at": "2022-02-28T15:33:26.083166Z",
            "updated_at": "2022-04-12T07:45:34.695803Z",
            "seo_title": "",
            "seo_title_en": null,
            "seo_keywords": "",
            "seo_keywords_en": null,
            "seo_description": "",
            "seo_description_en": "",
            "seo_author": "",
            "seo_author_en": null,
            "seo_og_type": "website",
            "seo_image": null,
            "lft": 1,
            "rght": 2,
            "tree_id": 1,
            "level": 0,
            "content": "",
            "content_en": "",
            "polymorphic_ctype": 10,
            "parent": null,
            "sites": [
                1
            ],
            "components": [
                {
                    "component_model": "TextComponent",
                    "object": {
                        "id": 1,
                        "title": "Текстовый блок",
                        "title_en": "Text block",
                        "created_at": "2022-04-11T15:35:24.829579Z",
                        "updated_at": "2022-04-11T15:37:09.898287Z",
                        "text_title": "",
                        "text": "Текст",
                        "text_en": "Text",
                        "polymorphic_ctype": 22,
                        "pages": [
                            1
                        ]
                    }
                },
                {
                    "component_model": "TextDescriptionComponent",
                    "object": {
                        "id": 2,
                        "title": "Описание рубрики",
                        "created_at": "2022-04-12T07:45:15.341862Z",
                        "updated_at": "2022-04-12T07:45:15.341886Z",
                        "text_title": "",
                        "text": "Текст",
                        "description": "Описание",
                        "polymorphic_ctype": 21,
                        "pages": [
                            1
                        ]
                    }
                }
            ]
        },
        "global": {}
    }
}

Templates:

If you want to override the base component template, add template parameter to component class:

# app/models/components.py

from garpix_page.models import BaseComponent

class TextComponent(BaseComponent):
    # ...
    template = 'text_component.html'
    # ...

In html you can use component object:

# templates/pages/components/default.html

<h1>{{ component.title }}</h1>

You can use gx_component tag in section with the component to add edit functionality for admin in template:

{% load gx_component %}
<section class="text-component" {% gx_component component %}>
    ...
</section>

Seo-templates

You can create seo-template from admin panel. If you set field value to Model title, the template will be used for pages only for those model. In other cases the template will be used for pages with the value of the field.

You can also specify the sites the template will be used on.

You can add fields which will be used for template keys, using get_seo_template_keys method and seo_template_keys_list class method.

class Page(BasePage):
    #...
    def get_seo_template_keys(self):
        seo_keys = super().get_seo_template_keys()
        seo_keys.update({
            'yourfield': self.yourfield
        })
        return seo_keys

    @classmethod
    def seo_template_keys_list(cls):
        return [('yourfield', 'your field title')]

Subpage url patterns

Sometimes we need to add static subpages like create, update etc. and it's not very convenient to create separate model/instance for each of them. For these purposes you can use subpage url patterns. Override url_patterns class method of BasePage model to add sub urls: Method url_patterns must return dict, which keys are names for models, which will be sent to api result; values are dicts with two keys: verbose_name - humanize model name, pattern - url pattern.

Example:

class Category(BasePage):
    # ...
    
    @classmethod
    def url_patterns(cls):
        patterns = super().url_patterns()
        patterns.update(
            {
                '{model_name}Create': {
                    'verbose_name': 'Создание {model_title}',
                    'pattern': '/create'
                },
                '{model_name}Reports': {
                    'verbose_name': 'Отчеты для {model_title}',
                    'pattern': '/reports'
                }
            }
        )
        return patterns

Now, if your project has Category page with url category, the project will also has two extra pages: category/create and category/reports.

If you need to use some query parameters in you urls, you can add them like any url parameters:

class Category(BasePage):
    # ...
    
    @classmethod
    def url_patterns(cls):
        patterns = super().url_patterns()
        patterns.update(
            {
                '{model_name}Create': {
                    'verbose_name': 'Создание {model_title}',
                    'pattern': '/create'
                },
                '{model_name}Reports': {
                    'verbose_name': 'Отчеты для {model_title}',
                    'pattern': '/reports'
                },
                '{model_name}Update': {
                    'verbose_name': 'Редактирование {model_title}',
                    'pattern': '/update/<id>'
                }
            }
        )
        return patterns

The given parameters will be stored in subpage_params field of page model, the key of pattern will be stored in subpage_key. Now you can use them in get_context to return some specific info depending on subpage_key:

class Category(BasePage):
    template = 'pages/category.html'

    def get_context(self, request=None, *args, **kwargs):
        context = super().get_context(request, *args, **kwargs)
        if self.subpage_key == '{model_name}Create':
            context.update({
                'some key': 'some text'
            })
        return context

    @classmethod
    def url_patterns(cls):
        patterns = super().url_patterns()
        patterns.update(
            {
                '{model_name}Create': {
                    'verbose_name': 'Создание {model_title}',
                    'pattern': '/create'
                },
                '{model_name}Update': {
                    'verbose_name': 'Редактирование {model_title}',
                    'pattern': '/update/<id>'
                }
            }
        )
        return patterns

    class Meta:
        verbose_name = "Категория"
        verbose_name_plural = "Категория"
        ordering = ('-created_at',)

Api result:

{
    "page_model": "CategoryCreate",
    "init_state": {
        "object": {
            "id": 16,
            "seo_title": "title-1",
            "seo_keywords": "",
            "seo_description": "",
            "seo_author": "",
            "seo_og_type": "website",
            "title": "title-1",
            "is_active": true,
            "display_on_sitemap": true,
            "slug": "title",
            "created_at": "2022-10-11T14:13:31.214166Z",
            "updated_at": "2023-02-07T06:07:43.179306Z",
            "seo_image": null
        },
        "components": [],
        "some key": "some text",
        "global": {}
    }
}

You also can add extra key permissions to your url pattern to override permissions for subpage:

from rest_framework.permissions import IsAuthenticated

class Category(BasePage):
    template = 'pages/category.html'

    def get_context(self, request=None, *args, **kwargs):
        context = super().get_context(request, *args, **kwargs)
        if self.subpage_key == '{model_name}Create':
            context.update({
                'some key': 'some text'
            })
        return context

    @classmethod
    def url_patterns(cls):
        patterns = super().url_patterns()
        patterns.update(
            {
                '{model_name}Create': {
                    'verbose_name': 'Создание {model_title}',
                    'pattern': '/create'
                },
                '{model_name}Update': {
                    'verbose_name': 'Редактирование {model_title}',
                    'pattern': '/update/<id>',
                    'permissions': [IsAuthenticated]
                }
            }
        )
        return patterns

    class Meta:
        verbose_name = "Категория"
        verbose_name_plural = "Категория"
        ordering = ('-created_at',)

Important!

Also, see this project for additional features (BaseListPage, BaseSearchPage, sitemap.xml, etc).

Changelog

See CHANGELOG.md.

Contributing

See CONTRIBUTING.md.

License

MIT

Project details


Release history Release notifications | RSS feed

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

garpix_page-2.48.2.tar.gz (1.5 MB view hashes)

Uploaded Source

Built Distribution

garpix_page-2.48.2-py3-none-any.whl (1.6 MB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page