# 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:
```bash
pip install garpix_page
```
Add the `garpix_page` and dependency packages to your `INSTALLED_APPS`:
```python
# 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:
```python
# settings.py
MIGRATION_MODULES = {
'garpix_page': 'app.migrations.garpix_page',
}
```
Run make migrations:
```bash
python manage.py makemigrations
```
Migrate:
```bash
python manage.py migrate
```
Add celery settings path to `settings.py`:
```python
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:
```python
# 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:
```python
# 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:
```python
# 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:
```python
# 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:
```html
# 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:
```python
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:
```python
API_URL = 'api'
```
Add to `urls.py` this:
```python
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:
```json
{
"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:
```json
{
"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
```python
# 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
```python
# 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:
```python
# 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:
```json
{
"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:
```python
# app/models/components.py
from garpix_page.models import BaseComponent
class TextComponent(BaseComponent):
# ...
template = 'text_component.html'
# ...
```
In html you can use `component` object:
```html
# 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:
```html
{% 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.
```python
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:
```python
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:
```python
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`:
```python
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:
```json
{
"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:
```python
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](CHANGELOG.md).
# Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md).
# License
[MIT](LICENSE)
Raw data
{
"_id": null,
"home_page": "https://github.com/garpixcms/garpix_page",
"name": "garpix-page",
"maintainer": "",
"docs_url": null,
"requires_python": "",
"maintainer_email": "",
"keywords": "",
"author": "Garpix LTD",
"author_email": "info@garpix.com",
"download_url": "https://files.pythonhosted.org/packages/b6/5a/5f20be3b993d3d745a4cd7ece8ee8cd26484704250cd0fd93326a8fe60fd/garpix_page-2.48.2.tar.gz",
"platform": null,
"description": "# Garpix Page\n\nConvenient page structure with any context and template.\nIt is suitable not only for a blog, but also for large sites with a complex presentation.\nSupports SEO.\n\n## Quickstart\n\nInstall with pip:\n\n```bash\npip install garpix_page\n```\n\nAdd the `garpix_page` and dependency packages to your `INSTALLED_APPS`:\n\n```python\n# settings.py\n\nINSTALLED_APPS = [\n 'modeltranslation',\n 'polymorphic_tree',\n 'polymorphic',\n 'mptt',\n # ... django.contrib.*\n 'django.contrib.sites',\n 'tabbed_admin',\n 'garpix_page',\n # third-party and your apps\n]\n\nSITE_ID=1\n\nLANGUAGE_CODE = 'en'\nUSE_DEFAULT_LANGUAGE_PREFIX = False\n\nLANGUAGES = (\n ('en', 'English'),\n)\n\nTEMPLATES = [\n {\n 'BACKEND': 'django.template.backends.django.DjangoTemplates',\n 'DIRS': ['templates'],\n 'APP_DIRS': True,\n 'OPTIONS': {\n 'context_processors': [\n 'django.template.context_processors.debug',\n 'django.template.context_processors.request',\n 'django.contrib.auth.context_processors.auth',\n 'django.contrib.messages.context_processors.messages',\n ],\n },\n },\n]\n\nMIDDLEWARE = [\n 'django.middleware.locale.LocaleMiddleware'\n]\n\n```\n\nPackage not included migrations, set path to migration directory. Don't forget create this directory (`app/migrations/garpix_page/`) and place empty `__init__.py`:\n\n```\napp/migrations/\napp/migrations/__init__.py # empty file\napp/migrations/garpix_page/__init__.py # empty file\n```\n\nAdd path to settings:\n\n```python\n# settings.py\n\nMIGRATION_MODULES = {\n 'garpix_page': 'app.migrations.garpix_page',\n}\n```\n\nRun make migrations:\n\n```bash\npython manage.py makemigrations\n```\n\nMigrate:\n\n```bash\npython manage.py migrate\n```\n\nAdd celery settings path to `settings.py`:\n\n```python\nGARPIXCMS_CELERY_SETTINGS = 'app.celery.app'\n```\n\nNow, you can create your models from `BasePage` and set template and context. See example below.\n\n### Important\n\n**Page (Model Page)** - model, subclass from `BasePage`. You create it yourself. There must be at least 1 descendant from BasePage.\n\n**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.\n\n**Template** - standard Django template.\n\n### Example\n\nUrls:\n\n```python\n# app/urls.py\n\nfrom django.contrib import admin\nfrom django.urls import path, re_path\nfrom django.conf.urls.i18n import i18n_patterns\nfrom garpix_page.views.page import PageView\nfrom multiurl import ContinueResolving, multiurl\nfrom django.http import Http404\nfrom django.conf import settings\nfrom garpix_page.views.index import IndexView\n\nurlpatterns = [\n path('admin/', admin.site.urls),\n]\n\nurlpatterns += i18n_patterns(\n multiurl(\n path('', PageView.as_view()),\n re_path(r'^(?P<url>.*?)$', PageView.as_view(), name='page'),\n re_path(r'^(?P<url>.*?)/$', PageView.as_view(), name='page'),\n path('', IndexView.as_view()),\n catch=(Http404, ContinueResolving),\n ),\n prefix_default_language=settings.USE_DEFAULT_LANGUAGE_PREFIX,\n)\n\n```\n\nModels:\n\n```python\n# app/models/page.py\n\nfrom django.db import models\nfrom garpix_page.models import BasePage\n\n\nclass Page(BasePage):\n content = models.TextField(verbose_name='Content', blank=True, default='')\n\n template = 'pages/default.html'\n\n class Meta:\n verbose_name = \"Page\"\n verbose_name_plural = \"Pages\"\n ordering = ('-created_at',)\n\n\n# app/models/category.py\n\nfrom garpix_page.models import BasePage\n\n\nclass Category(BasePage):\n template = 'pages/category.html'\n\n def get_context(self, request=None, *args, **kwargs):\n context = super().get_context(request, *args, **kwargs)\n posts = Post.on_site.filter(is_active=True, parent=kwargs['object'])\n context.update({\n 'posts': posts\n })\n return context\n\n class Meta:\n verbose_name = \"Category\"\n verbose_name_plural = \"Categories\"\n ordering = ('-created_at',)\n\n\n# app/models/post.py\n\nfrom django.db import models\nfrom garpix_page.models import BasePage\n\n\nclass Post(BasePage):\n content = models.TextField(verbose_name='Content', blank=True, default='')\n\n template = 'pages/post.html'\n\n class Meta:\n verbose_name = \"Post\"\n verbose_name_plural = \"Posts\"\n ordering = ('-created_at',)\n```\n\nAdmins:\n\n```python\n# app/admin/__init__.py\n\nfrom .page import PageAdmin\nfrom .category import CategoryAdmin\nfrom .post import PostAdmin\n\n\n# app/admin/page.py\n\nfrom ..models.page import Page\nfrom django.contrib import admin\nfrom garpix_page.admin import BasePageAdmin\n\n\n@admin.register(Page)\nclass PageAdmin(BasePageAdmin):\n pass\n\n# app/admin/category.py\n\nfrom ..models.category import Category\nfrom django.contrib import admin\nfrom garpix_page.admin import BasePageAdmin\n\n\n@admin.register(Category)\nclass CategoryAdmin(BasePageAdmin):\n pass\n\n# app/admin/post.py\n\nfrom ..models.post import Post\nfrom django.contrib import admin\nfrom garpix_page.admin import BasePageAdmin\n\n\n@admin.register(Post)\nclass PostAdmin(BasePageAdmin):\n pass\n\n```\n\nTranslations:\n\n```python\n# app/translation/__init__.py\n\nfrom .page import PageTranslationOptions\nfrom .category import CategoryTranslationOptions\nfrom .post import PostTranslationOptions\n\n# app/translation/page.py\n\nfrom modeltranslation.translator import TranslationOptions, register\nfrom ..models import Page\n\n\n@register(Page)\nclass PageTranslationOptions(TranslationOptions):\n fields = ('content',)\n\n\n# app/translation/category.py\n\nfrom modeltranslation.translator import TranslationOptions, register\nfrom ..models import Category\n\n\n@register(Category)\nclass CategoryTranslationOptions(TranslationOptions):\n fields = []\n\n# app/translation/post.py\n\nfrom modeltranslation.translator import TranslationOptions, register\nfrom ..models import Post\n\n\n@register(Post)\nclass PostTranslationOptions(TranslationOptions):\n fields = ('content',)\n\n```\n\nTemplates:\n\n```html\n# templates/base.html\n\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n {% include 'garpix_page/seo.html' %}\n</head>\n<body>\n{% include 'garpix_page/admin_toolbar.html' %}\n<main>\n {% block content %}404{% endblock %}\n {% block components %}\n {% for component in components %}\n {{ component.template }}\n {% include component.template %}\n {% endfor %}\n {% endblock %}\n</main>\n</body>\n</html>\n\n\n# templates/pages/default.html\n\n{% extends 'base.html' %}\n\n{% block content %}\n<h1>{{object.title}}</h1>\n<div>\n {{object.content|safe}}\n</div>\n{% endblock %}\n\n\n\n# templates/pages/category.html\n\n{% extends 'base.html' %}\n\n{% block content %}\n<h1>{{object.title}}</h1>\n{% for post in posts %}\n <div>\n <h3><a href=\"{{post.get_absolute_url}}\">{{post.title}}</a></h3>\n </div>\n{% endfor %}\n\n{% endblock %}\n\n\n\n# templates/pages/post.html\n\n{% extends 'base.html' %}\n\n{% block content %}\n<h1>{{object.title}}</h1>\n<div>\n {{object.content|safe}}\n</div>\n{% endblock %}\n\n```\n\nNow you can auth in admin panel and starting add pages.\n\nIf you need to use a serializer whose model is this page, use the get_serializer() method to avoid circular imports.\n\n## Page permissions\n\n\nIf you need to add login access to your model pages, add login_required static field to your model.\n\nTo add some user permissions to page, add permissions static field to your page model:\n\n```python\nclass Post(BasePage):\n content = models.TextField(verbose_name='Content', blank=True, default='')\n\n template = 'pages/post.html'\n\n login_required = True\n permissions = [IsAdminUser,]\n\n class Meta:\n verbose_name = \"Post\"\n verbose_name_plural = \"Posts\"\n ordering = ('-created_at',)\n\n```\n\n# API\n\nYou can use garpix_page with SPA sites.\n\nAdd to settings API_URL parameter:\n\n```python\nAPI_URL = 'api'\n```\n\nAdd to `urls.py` this:\n\n```python\nurlpatterns += [\n re_path(r'{}/page_models_list/$'.format(settings.API_URL), PageApiListView.as_view()),\n re_path(r'{}/page/(?P<slugs>.*)$'.format(settings.API_URL), PageApiView.as_view()),\n]\n```\n\nAnd you can test it:\n\n`http://localhost:8000/api/page/` - home page (empty slug)\n`http://localhost:8000/api/page/another_page` - another page (slug)\n`http://localhost:8000/api/page/kategoriya/post-1` - sub page (slug)\n\nExample answer:\n\n```json\n{\n \"page_model\": \"Post\",\n \"init_state\": {\n \"object\": {\n \"id\": 4,\n \"title\": \"post 1\",\n \"title_en\": \"post 1\",\n \"is_active\": true,\n \"display_on_sitemap\": true,\n \"slug\": \"post-1\",\n \"created_at\": \"2021-06-21T19:39:49.749460Z\",\n \"updated_at\": \"2021-06-21T19:39:49.749488Z\",\n \"seo_title\": \"\",\n \"seo_title_en\": null,\n \"seo_keywords\": \"\",\n \"seo_keywords_en\": null,\n \"seo_description\": \"\",\n \"seo_description_en\": \"\",\n \"seo_author\": \"\",\n \"seo_author_en\": null,\n \"seo_og_type\": \"website\",\n \"seo_image\": null,\n \"lft\": 2,\n \"rght\": 3,\n \"tree_id\": 3,\n \"level\": 1,\n \"content\": \"example\",\n \"content_en\": \"example\",\n \"polymorphic_ctype\": 11,\n \"parent\": 3,\n \"sites\": [\n 1\n ]\n }\n }\n}\n```\n\n## Error contexts\n\nModule consists of 3 reserved names for page models: `Page404`, `Page403` and `Page401`. These names are used for responses when corresponding error statuses are caught.\nExample answer on not found error:\n\n```json\n{\n \"page_model\": \"Page404\",\n \"init_state\": {\n \"object\": null,\n \"global\": {}\n }\n}\n```\n\n# Components\n\nIt is possible to compose a page from components. You can do this in the same way as creating pages.\n\nModel\n\n```python\n# app/models/components.py\nfrom django.db import models\n\nfrom garpix_page.models import BaseComponent\n\nclass TextComponent(BaseComponent):\n text = models.TextField(verbose_name='\u0422\u0435\u043a\u0441\u0442')\n\n class Meta:\n verbose_name = '\u0422\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442'\n verbose_name_plural = '\u0422\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b'\n\n def get_context_data(self, request): # add overriding this method to customize component's context\n context = super().get_context_data(request)\n return context\n\n```\nAdmin\n\n```python\n# app/admin/components.py\nfrom django.contrib import admin\n\nfrom garpix_page.admin.components.base_component import BaseComponentAdmin\nfrom app.models import TextComponent\n\n\n@admin.register(TextComponent)\nclass TextComponentAdmin(BaseComponentAdmin):\n pass\n\n```\n\nTranslations:\n\n```python\n# app/translation/components.py\n\nfrom modeltranslation.translator import TranslationOptions, register\nfrom app.models import TextComponent\n\n\n@register(TextComponent)\nclass TextComponentTranslationOptions(TranslationOptions):\n fields = ('text',)\n\n\n```\n\nBaseComponent 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).\nYou can override `get_contex_data` method to add some info to component context.\n\nExample answer with some components:\n\n```json\n{\n \"page_model\": \"Page\",\n \"init_state\": {\n \"object\": {\n \"id\": 1,\n \"title\": \"page\",\n \"title_en\": \"page\",\n \"is_active\": true,\n \"display_on_sitemap\": true,\n \"slug\": \"page\",\n \"created_at\": \"2022-02-28T15:33:26.083166Z\",\n \"updated_at\": \"2022-04-12T07:45:34.695803Z\",\n \"seo_title\": \"\",\n \"seo_title_en\": null,\n \"seo_keywords\": \"\",\n \"seo_keywords_en\": null,\n \"seo_description\": \"\",\n \"seo_description_en\": \"\",\n \"seo_author\": \"\",\n \"seo_author_en\": null,\n \"seo_og_type\": \"website\",\n \"seo_image\": null,\n \"lft\": 1,\n \"rght\": 2,\n \"tree_id\": 1,\n \"level\": 0,\n \"content\": \"\",\n \"content_en\": \"\",\n \"polymorphic_ctype\": 10,\n \"parent\": null,\n \"sites\": [\n 1\n ],\n \"components\": [\n {\n \"component_model\": \"TextComponent\",\n \"object\": {\n \"id\": 1,\n \"title\": \"\u0422\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439 \u0431\u043b\u043e\u043a\",\n \"title_en\": \"Text block\",\n \"created_at\": \"2022-04-11T15:35:24.829579Z\",\n \"updated_at\": \"2022-04-11T15:37:09.898287Z\",\n \"text_title\": \"\",\n \"text\": \"\u0422\u0435\u043a\u0441\u0442\",\n \"text_en\": \"Text\",\n \"polymorphic_ctype\": 22,\n \"pages\": [\n 1\n ]\n }\n },\n {\n \"component_model\": \"TextDescriptionComponent\",\n \"object\": {\n \"id\": 2,\n \"title\": \"\u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0440\u0443\u0431\u0440\u0438\u043a\u0438\",\n \"created_at\": \"2022-04-12T07:45:15.341862Z\",\n \"updated_at\": \"2022-04-12T07:45:15.341886Z\",\n \"text_title\": \"\",\n \"text\": \"\u0422\u0435\u043a\u0441\u0442\",\n \"description\": \"\u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435\",\n \"polymorphic_ctype\": 21,\n \"pages\": [\n 1\n ]\n }\n }\n ]\n },\n \"global\": {}\n }\n}\n```\n\nTemplates:\n\n\nIf you want to override the base component template, add `template` parameter to component class:\n\n```python\n# app/models/components.py\n\nfrom garpix_page.models import BaseComponent\n\nclass TextComponent(BaseComponent):\n # ...\n template = 'text_component.html'\n # ...\n\n```\n\nIn html you can use `component` object:\n```html\n# templates/pages/components/default.html\n\n<h1>{{ component.title }}</h1>\n```\n\nYou can use `gx_component` tag in section with the component to add edit functionality for admin in template:\n```html\n{% load gx_component %}\n<section class=\"text-component\" {% gx_component component %}>\n ...\n</section>\n```\n\n# Seo-templates\n\nYou can create seo-template from admin panel. \nIf you set `field` value to `Model title`, the template will be used for pages only for those model.\nIn other cases the template will be used for pages with the `value` of the `field`.\n\nYou can also specify the sites the template will be used on.\n\nYou can add fields which will be used for template keys, using `get_seo_template_keys` method and `seo_template_keys_list` class method.\n\n```python\nclass Page(BasePage):\n #...\n def get_seo_template_keys(self):\n seo_keys = super().get_seo_template_keys()\n seo_keys.update({\n 'yourfield': self.yourfield\n })\n return seo_keys\n\n @classmethod\n def seo_template_keys_list(cls):\n return [('yourfield', 'your field title')]\n```\n\n### Subpage url patterns\n\nSometimes 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.\nFor these purposes you can use subpage url patterns.\nOverride `url_patterns` class method of `BasePage` model to add sub urls:\nMethod `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.\n\nExample:\n\n```python\nclass Category(BasePage):\n # ...\n \n @classmethod\n def url_patterns(cls):\n patterns = super().url_patterns()\n patterns.update(\n {\n '{model_name}Create': {\n 'verbose_name': '\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 {model_title}',\n 'pattern': '/create'\n },\n '{model_name}Reports': {\n 'verbose_name': '\u041e\u0442\u0447\u0435\u0442\u044b \u0434\u043b\u044f {model_title}',\n 'pattern': '/reports'\n }\n }\n )\n return patterns\n\n```\nNow, if your project has `Category` page with url `category`, the project will also has two extra pages: `category/create` and `category/reports`.\n\nIf you need to use some query parameters in you urls, you can add them like any url parameters:\n\n```python\nclass Category(BasePage):\n # ...\n \n @classmethod\n def url_patterns(cls):\n patterns = super().url_patterns()\n patterns.update(\n {\n '{model_name}Create': {\n 'verbose_name': '\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 {model_title}',\n 'pattern': '/create'\n },\n '{model_name}Reports': {\n 'verbose_name': '\u041e\u0442\u0447\u0435\u0442\u044b \u0434\u043b\u044f {model_title}',\n 'pattern': '/reports'\n },\n '{model_name}Update': {\n 'verbose_name': '\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 {model_title}',\n 'pattern': '/update/<id>'\n }\n }\n )\n return patterns\n\n```\n\nThe given parameters will be stored in `subpage_params` field of page model, the key of pattern will be stored in `subpage_key`.\nNow you can use them in `get_context` to return some specific info depending on `subpage_key`:\n\n```python\nclass Category(BasePage):\n template = 'pages/category.html'\n\n def get_context(self, request=None, *args, **kwargs):\n context = super().get_context(request, *args, **kwargs)\n if self.subpage_key == '{model_name}Create':\n context.update({\n 'some key': 'some text'\n })\n return context\n\n @classmethod\n def url_patterns(cls):\n patterns = super().url_patterns()\n patterns.update(\n {\n '{model_name}Create': {\n 'verbose_name': '\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 {model_title}',\n 'pattern': '/create'\n },\n '{model_name}Update': {\n 'verbose_name': '\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 {model_title}',\n 'pattern': '/update/<id>'\n }\n }\n )\n return patterns\n\n class Meta:\n verbose_name = \"\u041a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u044f\"\n verbose_name_plural = \"\u041a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u044f\"\n ordering = ('-created_at',)\n\n```\n\nApi result:\n\n```json\n\n{\n \"page_model\": \"CategoryCreate\",\n \"init_state\": {\n \"object\": {\n \"id\": 16,\n \"seo_title\": \"title-1\",\n \"seo_keywords\": \"\",\n \"seo_description\": \"\",\n \"seo_author\": \"\",\n \"seo_og_type\": \"website\",\n \"title\": \"title-1\",\n \"is_active\": true,\n \"display_on_sitemap\": true,\n \"slug\": \"title\",\n \"created_at\": \"2022-10-11T14:13:31.214166Z\",\n \"updated_at\": \"2023-02-07T06:07:43.179306Z\",\n \"seo_image\": null\n },\n \"components\": [],\n \"some key\": \"some text\",\n \"global\": {}\n }\n}\n```\n\nYou also can add extra key `permissions` to your url pattern to override permissions for subpage:\n\n```python\nfrom rest_framework.permissions import IsAuthenticated\n\nclass Category(BasePage):\n template = 'pages/category.html'\n\n def get_context(self, request=None, *args, **kwargs):\n context = super().get_context(request, *args, **kwargs)\n if self.subpage_key == '{model_name}Create':\n context.update({\n 'some key': 'some text'\n })\n return context\n\n @classmethod\n def url_patterns(cls):\n patterns = super().url_patterns()\n patterns.update(\n {\n '{model_name}Create': {\n 'verbose_name': '\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 {model_title}',\n 'pattern': '/create'\n },\n '{model_name}Update': {\n 'verbose_name': '\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 {model_title}',\n 'pattern': '/update/<id>',\n 'permissions': [IsAuthenticated]\n }\n }\n )\n return patterns\n\n class Meta:\n verbose_name = \"\u041a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u044f\"\n verbose_name_plural = \"\u041a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u044f\"\n ordering = ('-created_at',)\n\n```\n\n## Important!\n\nAlso, see this project for additional features (`BaseListPage`, `BaseSearchPage`, `sitemap.xml`, etc).\n\n# Changelog\n\nSee [CHANGELOG.md](CHANGELOG.md).\n\n# Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md).\n\n# License\n\n[MIT](LICENSE)\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "",
"version": "2.48.2",
"project_urls": {
"Homepage": "https://github.com/garpixcms/garpix_page"
},
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "9156c4475752cb473a08b06db4a18b42d6852ee392f9d63771e20728ee8224ca",
"md5": "617f42016dcd14016108343dacffcfc0",
"sha256": "cdd208aaac340a749efe1e388d547e1a73646d8abdaab0b29e6a959381ed2e10"
},
"downloads": -1,
"filename": "garpix_page-2.48.2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "617f42016dcd14016108343dacffcfc0",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 1616494,
"upload_time": "2023-09-19T10:01:57",
"upload_time_iso_8601": "2023-09-19T10:01:57.029991Z",
"url": "https://files.pythonhosted.org/packages/91/56/c4475752cb473a08b06db4a18b42d6852ee392f9d63771e20728ee8224ca/garpix_page-2.48.2-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "b65a5f20be3b993d3d745a4cd7ece8ee8cd26484704250cd0fd93326a8fe60fd",
"md5": "5701212c59dd0402bc62018816a51069",
"sha256": "f642c6177b43067448d000168ef459c0c7ce0f699d9ca3f5c6f218d3af4d1b52"
},
"downloads": -1,
"filename": "garpix_page-2.48.2.tar.gz",
"has_sig": false,
"md5_digest": "5701212c59dd0402bc62018816a51069",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 1544350,
"upload_time": "2023-09-19T10:01:59",
"upload_time_iso_8601": "2023-09-19T10:01:59.129561Z",
"url": "https://files.pythonhosted.org/packages/b6/5a/5f20be3b993d3d745a4cd7ece8ee8cd26484704250cd0fd93326a8fe60fd/garpix_page-2.48.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-09-19 10:01:59",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "garpixcms",
"github_project": "garpix_page",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "garpix-page"
}