====================
djangocms-spa-vue-js
====================
This package prepares your django CMS and vue.js project to create a single-page application (SPA). Use it together
with the base package `djangocms-spa`_.
A template tag renders a list of all available routes that are used by vue-router. Contents of other pages are
requested asynchronously and delivered as JSON through a REST-API.
Make sure you read the docs of djangocms-spa.
.. _`djangocms-spa`: https://github.com/dreipol/djangocms-spa/
Quickstart
----------
Install djangocms-spa-vue-js::
pip install djangocms-spa-vue-js
Add it to your ``INSTALLED_APPS``:
.. code-block:: python
INSTALLED_APPS = (
...
'djangocms_spa',
'djangocms_spa_vue_js',
...
)
Add the URL pattern form the API:
.. code-block:: python
urlpatterns = [
...
url(r'^api/', include('djangocms_spa.urls', namespace='api')),
...
]
Render your vue.js router in your template::
{% load router_tags %}
{% vue_js_router %}
Apphooks
--------
You need to consider a couple of things when using apphooks. Let's assume you have an event model.
.. code-block:: python
class Event(DjangocmsVueJsMixin):
name = models.CharField(max_length=255, verbose_name=_('Name'))
def get_frontend_list_data_dict(self, request, editable=False, placeholder_name=''):
# Returns the data for your list view.
data = super(Event, self).get_frontend_list_data_dict(request=request, editable=editable, placeholder_name=placeholder_name)
data['content'].update({
'name': self.name,
})
return data
def get_frontend_detail_data_dict(self, request, editable=False):
# Returns the data for your detail view.
data = super(Event, self).get_frontend_detail_data_dict(request, editable)
# Prepare the content of your model instance. We use the same structure like the placeholder data of a CMS page.
content_container = {
'type': 'generic',
'content': {
'name': self.name
}
}
# Add support for the CMS frontend editing
if editable:
content_container.update(
self.get_cms_placeholder_json(request=request, placeholder_name='cms-plugin-events-content')
)
# Put the data inside a container like any other CMS placeholder data.
data['containers']['content'] = content_container
return data
def get_absolute_url(self):
# Return the URL of your detail view.
return reverse('event_detail', kwargs={'pk': self.pk})
def get_api_detail_url(self):
# Return the API URL of your detail view.
return reverse('event_detail_api', kwargs={'pk': self.pk})
def get_detail_view_component(self):
# Return the name of your vue component.
return 'cmp-event-detail'
def get_detail_path_pattern(self):
# Return the path pattern of your named vue route.
return 'events/:pk'
def get_url_params(self):
# Return the params that are needed to access your named vue route.
return {
'pk': self.pk
}
All of your views need to be attached to the menu, even if they are not actually rendered in your site navigation.
If the CMS page holding your apphook uses a custom view, you need this configuration:
.. code-block:: python
DJANGOCMS_SPA_VUE_JS_APPHOOKS_WITH_ROOT_URL = ['<my_apphook_name>']
Your ``cms_menus.py`` might looks like this:
.. code-block:: python
class EventMenu(CMSAttachMenu):
name = _('Events')
def get_nodes(self, request):
nodes = []
counter = 1
is_draft = self.instance.publisher_is_draft
is_edit = hasattr(request, 'toolbar') and request.user.is_staff and request.toolbar.edit_mode
# We don't want to parse the instance in live and draft mode. Depending on the request user we return the
# corresponding version.
if (not is_edit and not is_draft) or (is_edit and is_draft):
# Let's add the list view
nodes.append(
NavigationNode(
title='Event List',
url=reverse('event_list'),
id=1,
attr={
'component': 'cmp-event-list',
'vue_js_router_name': 'event-list',
'fetch_url': reverse('event_list_api'),
'absolute_url': reverse('event_list'),
'named_route_path_pattern': ':pk', # Used to group routes (dynamic route matching)
'login_required': True # Hide a navigation node for unauthorized users
}
)
)
counter += 1
for event in Event.objects.all():
nodes.append(
NavigationNode(
title=event.name,
url=event.get_absolute_url(),
id=counter,
attr=event.get_cms_menu_node_attributes(),
parent_id=1
)
)
counter += 1
return nodes
menu_pool.register_menu(EventMenu)
This is an example of a simple template view. Each view that you have needs an API view that returns the JSON data only.
.. code-block:: python
from djangocms_spa.views import SpaApiView
from djangocms_spa_vue_js.views import VueRouterView
class ContentMixin(object):
template_name = 'index.html'
def get_fetched_data(self):
data = {
'containers': {
'content': {
'type': 'generic',
'content': {
'key': 'value'
}
}
}
}
return data
class MyTemplateView(ContentMixin, VueRouterView):
fetch_url = reverse_lazy('content_api') # URL of the API view.
class MyTemplateApiView(ContentMixin, SpaApiView):
pass
Your list view looks like this:
.. code-block:: python
from djangocms_spa.views import SpaListApiView
from djangocms_spa_vue_js.views import VueRouterListView
class EventListView(VueRouterListView):
fetch_url = reverse_lazy('event_list_api')
model = Event
template_name = 'event_list.html'
class EventListAPIView(SpaListApiView):
model = Event
template_name = 'event_list.html'
Your detail view looks like this:
.. code-block:: python
from djangocms_spa.views import SpaDetailApiView
from djangocms_spa_vue_js.views import VueRouterDetailView
class EventDetailView(VueRouterDetailView):
model = Event
template_name = 'event_detail.html'
def get_fetch_url(self):
return reverse('event_detail_api', kwargs={'pk': self.object.pk})
class EventDetailAPIView(SpaDetailApiView):
model = Event
template_name = 'event_detail.html'
The router object
-----------------
The server needs to prepare the routes for the frontend. The easiest way to do this is by iterating over the CMS
menu. In order to bring all available routes to the menu, you have to register all your custom URLs as a menu too.
A template tag renders a JS object like this.
.. code-block:: json
{
"routes": [
{
"api": {
"fetch": "/api/pages/",
"query": {
"partials": ["menu", "footer"]
}
},
"component": "index",
"name": "cms-page-1",
"path": "/"
},
{
"api": {
"fetched": {
"partials": {
"menu": {
"type": "generic",
"content": {
"menu": [
{
"path": "/",
"label": "Home",
"children": [
{
"path": "/about",
"label": "About",
"children": [
{
"path": "/contact",
"label": "Contact"
}
]
}
]
}
]
}
},
"footer": {
"type": "cmp-footer",
"plugins": [
{
"type": "cmp-footer-text",
"position": 0,
"content": {
"text": "Lorem ipsum dolor sit amet, nam et modus tollit."
}
}
]
}
},
"data": {
"meta": {
"description": "",
"title": "Content-Plugins"
},
"containers": {
"main": {
"type": "cmp-main",
"plugins": [
{
"type": "cmp-text",
"position": 0,
"content": {
"text": "Ex vim saperet habemus, et eum impetus mentitum, cum purto dolores similique ei."
}
}
]
}
},
"title": "About"
}
},
"query": {
"partials": ["menu", "footer"]
}
},
"component": "content-with-section-navigation",
"name": "cms-page-2",
"path": "/about"
},
{
"api": {
"fetch": "/api/pages/about/contact",
"query": {
"partials": ["menu", "meta", "footer"]
}
},
"component": "content-with-section-navigation",
"name": "cms-page-3",
"path": "/about/contact"
}
]
}
Debugging
---------
If you need to debug the router object, this middleware is probably pretty helpful:
.. code-block:: python
MIDDLEWARE += (
'djangocms_spa_vue_js.middleware.RouterDebuggingMiddleware',
)
Credits
-------
Tools used in rendering this package:
* Cookiecutter_
* `cookiecutter-djangopackage`_
.. _Cookiecutter: https://github.com/audreyr/cookiecutter
.. _`cookiecutter-djangopackage`: https://github.com/pydanny/cookiecutter-djangopackage
History
-------
Raw data
{
"_id": null,
"home_page": "https://github.com/dreipol/djangocms-spa-vue-js",
"name": "djangocms-spa-vue-js",
"maintainer": null,
"docs_url": null,
"requires_python": null,
"maintainer_email": null,
"keywords": "djangocms-spa-vue-js",
"author": "dreipol GmbH",
"author_email": "dev@dreipol.ch",
"download_url": "https://files.pythonhosted.org/packages/e0/c0/a5b7bd72ba6ce68ce37145aa98c9010149eab60a5918088fbec95a2422f0/djangocms-spa-vue-js-0.1.29.tar.gz",
"platform": null,
"description": "====================\ndjangocms-spa-vue-js\n====================\n\nThis package prepares your django CMS and vue.js project to create a single-page application (SPA). Use it together\nwith the base package `djangocms-spa`_.\n\nA template tag renders a list of all available routes that are used by vue-router. Contents of other pages are\nrequested asynchronously and delivered as JSON through a REST-API.\n\nMake sure you read the docs of djangocms-spa.\n\n.. _`djangocms-spa`: https://github.com/dreipol/djangocms-spa/\n\n\nQuickstart\n----------\n\nInstall djangocms-spa-vue-js::\n\n pip install djangocms-spa-vue-js\n\nAdd it to your ``INSTALLED_APPS``:\n\n.. code-block:: python\n\n INSTALLED_APPS = (\n ...\n 'djangocms_spa',\n 'djangocms_spa_vue_js',\n ...\n )\n\nAdd the URL pattern form the API:\n\n.. code-block:: python\n\n urlpatterns = [\n ...\n url(r'^api/', include('djangocms_spa.urls', namespace='api')),\n ...\n ]\n\nRender your vue.js router in your template::\n\n {% load router_tags %}\n {% vue_js_router %}\n\n\n\nApphooks\n--------\n\nYou need to consider a couple of things when using apphooks. Let's assume you have an event model.\n\n.. code-block:: python\n\n class Event(DjangocmsVueJsMixin):\n name = models.CharField(max_length=255, verbose_name=_('Name'))\n\n def get_frontend_list_data_dict(self, request, editable=False, placeholder_name=''):\n # Returns the data for your list view.\n data = super(Event, self).get_frontend_list_data_dict(request=request, editable=editable, placeholder_name=placeholder_name)\n data['content'].update({\n 'name': self.name,\n })\n return data\n\n def get_frontend_detail_data_dict(self, request, editable=False):\n # Returns the data for your detail view.\n data = super(Event, self).get_frontend_detail_data_dict(request, editable)\n\n # Prepare the content of your model instance. We use the same structure like the placeholder data of a CMS page.\n content_container = {\n 'type': 'generic',\n 'content': {\n 'name': self.name\n }\n }\n\n # Add support for the CMS frontend editing\n if editable:\n content_container.update(\n self.get_cms_placeholder_json(request=request, placeholder_name='cms-plugin-events-content')\n )\n\n # Put the data inside a container like any other CMS placeholder data.\n data['containers']['content'] = content_container\n\n return data\n\n def get_absolute_url(self):\n # Return the URL of your detail view.\n return reverse('event_detail', kwargs={'pk': self.pk})\n\n def get_api_detail_url(self):\n # Return the API URL of your detail view.\n return reverse('event_detail_api', kwargs={'pk': self.pk})\n\n def get_detail_view_component(self):\n # Return the name of your vue component.\n return 'cmp-event-detail'\n\n def get_detail_path_pattern(self):\n # Return the path pattern of your named vue route.\n return 'events/:pk'\n\n def get_url_params(self):\n # Return the params that are needed to access your named vue route.\n return {\n 'pk': self.pk\n }\n\n\nAll of your views need to be attached to the menu, even if they are not actually rendered in your site navigation.\nIf the CMS page holding your apphook uses a custom view, you need this configuration:\n\n.. code-block:: python\n\n DJANGOCMS_SPA_VUE_JS_APPHOOKS_WITH_ROOT_URL = ['<my_apphook_name>']\n\n\nYour ``cms_menus.py`` might looks like this:\n\n.. code-block:: python\n\n class EventMenu(CMSAttachMenu):\n name = _('Events')\n\n def get_nodes(self, request):\n nodes = []\n counter = 1\n is_draft = self.instance.publisher_is_draft\n is_edit = hasattr(request, 'toolbar') and request.user.is_staff and request.toolbar.edit_mode\n\n # We don't want to parse the instance in live and draft mode. Depending on the request user we return the\n # corresponding version.\n if (not is_edit and not is_draft) or (is_edit and is_draft):\n # Let's add the list view\n nodes.append(\n NavigationNode(\n title='Event List',\n url=reverse('event_list'),\n id=1,\n attr={\n 'component': 'cmp-event-list',\n 'vue_js_router_name': 'event-list',\n 'fetch_url': reverse('event_list_api'),\n 'absolute_url': reverse('event_list'),\n 'named_route_path_pattern': ':pk', # Used to group routes (dynamic route matching)\n 'login_required': True # Hide a navigation node for unauthorized users\n }\n )\n )\n counter += 1\n\n for event in Event.objects.all():\n nodes.append(\n NavigationNode(\n title=event.name,\n url=event.get_absolute_url(),\n id=counter,\n attr=event.get_cms_menu_node_attributes(),\n parent_id=1\n )\n )\n counter += 1\n\n return nodes\n\n menu_pool.register_menu(EventMenu)\n\n\nThis is an example of a simple template view. Each view that you have needs an API view that returns the JSON data only.\n\n.. code-block:: python\n\n from djangocms_spa.views import SpaApiView\n from djangocms_spa_vue_js.views import VueRouterView\n\n class ContentMixin(object):\n template_name = 'index.html'\n\n def get_fetched_data(self):\n data = {\n 'containers': {\n 'content': {\n 'type': 'generic',\n 'content': {\n 'key': 'value'\n }\n }\n }\n }\n return data\n\n\n class MyTemplateView(ContentMixin, VueRouterView):\n fetch_url = reverse_lazy('content_api') # URL of the API view.\n\n\n class MyTemplateApiView(ContentMixin, SpaApiView):\n pass\n\n\nYour list view looks like this:\n\n.. code-block:: python\n\n from djangocms_spa.views import SpaListApiView\n from djangocms_spa_vue_js.views import VueRouterListView\n\n class EventListView(VueRouterListView):\n fetch_url = reverse_lazy('event_list_api')\n model = Event\n template_name = 'event_list.html'\n\n\n class EventListAPIView(SpaListApiView):\n model = Event\n template_name = 'event_list.html'\n\n\nYour detail view looks like this:\n\n.. code-block:: python\n\n from djangocms_spa.views import SpaDetailApiView\n from djangocms_spa_vue_js.views import VueRouterDetailView\n\n class EventDetailView(VueRouterDetailView):\n model = Event\n template_name = 'event_detail.html'\n\n def get_fetch_url(self):\n return reverse('event_detail_api', kwargs={'pk': self.object.pk})\n\n\n class EventDetailAPIView(SpaDetailApiView):\n model = Event\n template_name = 'event_detail.html'\n\n\nThe router object\n-----------------\n\nThe server needs to prepare the routes for the frontend. The easiest way to do this is by iterating over the CMS\nmenu. In order to bring all available routes to the menu, you have to register all your custom URLs as a menu too.\nA template tag renders a JS object like this.\n\n.. code-block:: json\n\n {\n \"routes\": [\n {\n \"api\": {\n \"fetch\": \"/api/pages/\",\n \"query\": {\n \"partials\": [\"menu\", \"footer\"]\n }\n },\n \"component\": \"index\",\n \"name\": \"cms-page-1\",\n \"path\": \"/\"\n },\n {\n \"api\": {\n \"fetched\": {\n \"partials\": {\n \"menu\": {\n \"type\": \"generic\",\n \"content\": {\n \"menu\": [\n {\n \"path\": \"/\",\n \"label\": \"Home\",\n \"children\": [\n {\n \"path\": \"/about\",\n \"label\": \"About\",\n \"children\": [\n {\n \"path\": \"/contact\",\n \"label\": \"Contact\"\n }\n ]\n }\n ]\n }\n ]\n }\n },\n \"footer\": {\n \"type\": \"cmp-footer\",\n \"plugins\": [\n {\n \"type\": \"cmp-footer-text\",\n \"position\": 0,\n \"content\": {\n \"text\": \"Lorem ipsum dolor sit amet, nam et modus tollit.\"\n }\n }\n ]\n }\n },\n \"data\": {\n \"meta\": {\n \"description\": \"\",\n \"title\": \"Content-Plugins\"\n },\n \"containers\": {\n \"main\": {\n \"type\": \"cmp-main\",\n \"plugins\": [\n {\n \"type\": \"cmp-text\",\n \"position\": 0,\n \"content\": {\n \"text\": \"Ex vim saperet habemus, et eum impetus mentitum, cum purto dolores similique ei.\"\n }\n }\n ]\n }\n },\n \"title\": \"About\"\n }\n },\n \"query\": {\n \"partials\": [\"menu\", \"footer\"]\n }\n },\n \"component\": \"content-with-section-navigation\",\n \"name\": \"cms-page-2\",\n \"path\": \"/about\"\n },\n {\n \"api\": {\n \"fetch\": \"/api/pages/about/contact\",\n \"query\": {\n \"partials\": [\"menu\", \"meta\", \"footer\"]\n }\n },\n \"component\": \"content-with-section-navigation\",\n \"name\": \"cms-page-3\",\n \"path\": \"/about/contact\"\n }\n ]\n }\n\n\nDebugging\n---------\n\nIf you need to debug the router object, this middleware is probably pretty helpful:\n\n.. code-block:: python\n\n MIDDLEWARE += (\n 'djangocms_spa_vue_js.middleware.RouterDebuggingMiddleware',\n )\n\n\nCredits\n-------\n\nTools used in rendering this package:\n\n* Cookiecutter_\n* `cookiecutter-djangopackage`_\n\n.. _Cookiecutter: https://github.com/audreyr/cookiecutter\n.. _`cookiecutter-djangopackage`: https://github.com/pydanny/cookiecutter-djangopackage\n\n\n\n\nHistory\n-------",
"bugtrack_url": null,
"license": "MIT",
"summary": "This package prepares your django CMS and vue.js project to create a single-page application (SPA).",
"version": "0.1.29",
"project_urls": {
"Homepage": "https://github.com/dreipol/djangocms-spa-vue-js"
},
"split_keywords": [
"djangocms-spa-vue-js"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "e0c0a5b7bd72ba6ce68ce37145aa98c9010149eab60a5918088fbec95a2422f0",
"md5": "e35d1c6fd70a110951e1be22cbb1edd4",
"sha256": "b81ce1e539c0ac7e9bdb71ea778b77152b20829064bf449c673a8bfa8931dc71"
},
"downloads": -1,
"filename": "djangocms-spa-vue-js-0.1.29.tar.gz",
"has_sig": false,
"md5_digest": "e35d1c6fd70a110951e1be22cbb1edd4",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 14729,
"upload_time": "2024-07-03T07:39:40",
"upload_time_iso_8601": "2024-07-03T07:39:40.196703Z",
"url": "https://files.pythonhosted.org/packages/e0/c0/a5b7bd72ba6ce68ce37145aa98c9010149eab60a5918088fbec95a2422f0/djangocms-spa-vue-js-0.1.29.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-07-03 07:39:40",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "dreipol",
"github_project": "djangocms-spa-vue-js",
"travis_ci": true,
"coveralls": true,
"github_actions": false,
"requirements": [],
"tox": true,
"lcname": "djangocms-spa-vue-js"
}