How to create an article list view in django-cms - django

I am an absolute beginner in django-cms, just acquired some pieces of knowledge to create templates. Just wondering, how to create a portal page that has a few acticles in each different categories?
Please simply point out a practical way to do, no real code is needed.
Thank you.

As others have pointed out, the way to do this is by hooking your CMS page to another set of views. Django-CMS provides application hooks:
#cms_app.py
from cms.app_base import CMSApp
from cms.apphook_pool import apphook_pool
class YourModuleApp(CMSApp):
name = 'My App MOdule'
urls = ['my_app.urls']
apphook_pool.register(YourModuleApp)
So, if you had a module called "my_app" with a urls.py in it, Django-CMS will add those patterns to the page. Look in the "Advanced Settings" section of the page in admin for the application drop-down menu.
Once the app is hooked to the page, Django-CMS will pull any content and the layout template from the information it holds, then hand off processing to the additional URL patterns that are hooked to it. That's how you can pull in another model, add a form, handle a POST, etc.

You could just do it the normal Django way. Create a normal Django app, with a URL pointing to a view that renders a template. The view could look like this:
from django.shortcuts import render
from cms.models import Page
def articles(request):
pages = Page.objects.public()
render(request, 'example.html', {'pages': pages})
And the template could look like this:
{% load cms_tags %}
{% for page in pages %}
<p>{% page_attribute "page_title" page %}</p>
{% endfor %}
You could stop here. Or you could have...
Additional Django CMS integration with AppHooks
Do you want your non-developer content managers to be able to put a list of articles wherever they want? This is where AppHooks come in.
Create a CMSApp class in the file appname/cms_app.py like this:
from cms.app_base import CMSApp
from cms.apphook_pool import apphook_pool
from django.utils.translation import ugettext_lazy as _
class ArticleListApp(CMSApp):
app_name = 'articlelist'
name = _('Article List')
def get_urls(self, page=None, language=None, **kwargs):
return ['articlelist.urls']
apphook_pool.register(YourModuleApp)
Delete the URL entry in your project-wide urls.py as you no longer need it. Your app's urls.py needs to include a view for the regex ^$.
Now you or any content manager user with necessary permissions can create a page in the admin interface, and modify the advanced settings to select the "Article List" application:
One gotcha is that this will have no effect until the page is published (as well as all of its ancestor pages).

Related

How to correctly render multiple apps on the main page

So I'm currently learning to use Django, and I'm wondering how to correctly split up parts of the functionality while still displaying it on the main page.
For example, I want the header + navigation, a calendar and recent blog articles on the main index page.
On the view article page I'd for example have the header + nav, the calendar and a single article with a comment section.
Now reading the tutorials, if I understand them correctly, I'd split the functionality in a header app, a calendar app and the blog app itself while glueing it together with a core app.
But what I don't understand is how to render apps/other views in the main app. All the ways I found only specify templates itself or look very hacky, so apparently that doesn't seem to be the common way to go.
So there are multiple views are work here:
1.The views that you want to show
2.The view that will render the page, displaying all of the views (lets call this the 'main view'.
The first step is to import all of the other views / models into the views.py file that the main view resides in.
from blog.models import Post
from calendar.models import Calendar
Now you can edit your mainview to acces this data. For example:
class Mainview(TemplateView):
template_name = 'app/homepage.html'
def get_context_data(self, **kwargs):
#This will only show the latest post
data['posts'] = Post.objects.all().order_by('-id')[:1]
data['calendar'] = Calendar.objects.all()
return data
Now you can acces the data from the other apps in your template using the {{ }} tags, for example - you could do something like this:
{% for post object in post %}
{{ post.title }}
{{ post.content}}
{% endfor %}

django-cms getting the page context in an application hook

Consider this cms_app.py
from django.utils.translation import ugettext_lazy as _
from cms.app_base import CMSApp
from cms.apphook_pool import apphook_pool
class FooHook(CMSApp):
name = _("FooHook Plugin")
urls = ["foo.urls"]
apphook_pool.register(FooHook)
The foo app, has a views model which contains to default django views, ListView and DetailView.. These have their default templates, inheriting from a base.html, these are foo_list.html and foo_detail.html..
The documentation is completely unclear how to get, for example the cms page's sidebar content or page context variable at all, within those templates..
Feincms has the {% fragment %} template tag for this purpose, how can one achieve this in django-cms?
To access the page you can use {{ request.current_page }}
To use the plugin system in your templates use {% static_placeholder my_name %} instead of the {% placeholder %} tags.
It is always preferable to use the static_placeholders in apphooks as you do not know what template and what placeholders the page actually has.

Showing extended page attributes from reverse_id

I've made an extra image field as shown by #Timmy O'Mahony on
How to add some extra fields to the page in django-cms? (in django admin panel)
I'd like to call this image field from another page using its reverse_id, much like
{% page_attribute "extended_page_options.image" "my_page_reverse_id" %}
Do I need a custom template tag? How can I do it?
I am using django-cms 2.4.3 and django 1.5.5
EDIT:
models.py
from django.db import models
from django.utils.translation import ugettext_lazy as _
from cms.models.pagemodel import Page
from filer.fields.image import FilerImageField
class ExtendedPage(models.Model):
page = models.ForeignKey(Page, unique=True, verbose_name=_("Page"), editable=False, related_name='extended_fields')
image = FilerImageField(blank=True, null=True)
In a recent project, I think I solved this with a context_processor as follows:
from django.conf import settings
def page_extension(request):
"""Puts settings.DEBUG and the page_extension, if present and puts it into
the context"""
context = {'debug': settings.DEBUG}
if request.current_page:
page = request.current_page
try:
context.update({'page_extension': page.page_extension})
except:
try:
context.update({'page_extension': page.publisher_public.page_extension})
except:
pass
return context
In this case, I've also used the opportunity to also add the value of settings.DEBUG, this is optional of course.
Then, just add this to your settings file as:
TEMPLATE_CONTEXT_PROCESSORS = (
...
'path.to.above.code.page_extension',
)
Then, next step, is just access the value in your templates:
{% if page_extension and page_extension.my_page_property %}{{ page_extension.my_page_property }}{% endif %}
First thing, if you are starting out the new project, go for django cms 3.0, it has lots of new and exciting features than the previous release.
Back to your question, go to the advanced settings of the page which contains the actual image field and add the id. Then on other page do like this:
{% page_attribute "field_name" "my_page_reverse_id" %}

Add links to navigation based on INSTALLED_APPS

I have a project that has a number of apps. These apps translate into modules that perform different functions for the end user.
Now each deployment of the project may have certain apps enabled or disabled.
What I'd like to achieve is a navigation list of links that only displays links for apps that are in INSTALLED_APPS.
For example deployment 1 has App1 and App2 listed in INSTALLED_APPS so would have a navigation something like this:
Link to App1's view
Link to App2's view
And deployment 2 has App2 and App3 installed so should show a navigation something like this:
Link to App2's view
Link to App3's view
Without having a navigation defined in a base template and editing it for each deployment, I can't see a way of doing this. Even by using { block.super }, this seems to not allow 2 apps to be installed as each child template would append to the parent.
This is strictly from the top of my head, so it may not be the "best" or most appropriate way.
First, if you're going to rely on INSTALLED_APPS you should actually implement something along the lines of:
MY_INSTALLED_APPS = (
'app1',
'app2',
)
INSTALLED_APPS = (
# other installed apps
) + MY_INSTALLED_APPS
Then, instead of having to figure out which of the items in INSTALLED_APPS are yours and which are third-party, you just use MY_INSTALLED_APPS instead for things like building your menus.
Second, in Django, apps aren't tied to views in any meaningful way. There's no concept of a "default" view, and you can't simply link to an app. However, you can some what achieve this idea through the use of namespaces and a convention for view naming, specifically, all your apps will at least have a urlpattern named "index", for example.
Then, in your urls.py you create urlpatterns like:
url(r'^app1/', include('app1.urls', namespace='app1', app_name='app1'),
url(r'^app2/', include('app2.urls', namespace='app2', app_name='app2'),
# etc
In each app, you create a urls.py that has at least one urlpattern:
url(r'^$', some_view, name='index'),
This means that the going to /app1/ in your browser would then load some_view and you can reference this view in your code with a name like: app1:index.
The tricky part is using this in your templates. Django 1.5 will add the ability to use context variables in the {% url %) tag for the view name. As of Django 1.3, you can use this behavior as well via {% load url from future %}. However, even that only gets you part-way to what you need.
In your template, you'll need to loop through the values of MY_INSTALLED_APPS and construct the links. The following should work in Django 1.3-1.4 via {% load url from future %} or Django 1.5:
{% for app in apps %}
{{ app }}
{% endfor %}
The other way to accomplish this is with a template filter, which is your only option in Django <1.3, and may still be preferable in later versions. Something like:
#register.filter
def default_url_for_app(app):
return reverse(app+':index')
And, in your template:
{% for app in apps %}
{{ app }}
{% endfor %}
You can simple import your settings.py file and iterate over the INSTALLED_APPS list.
from django.conf import settings
for installed_app in settings.INSTALLED_APPS:
But I'm also in doubt if this is a smart decision to implement your application as such.
What you really want here is some kind of app registration system. That is, for each app that's loaded (ie included in INSTALLED_APPS), register its homepage with the list of links.
Rather than doing all this automatically, I would follow the example of the admin application and get each app to call a register function on first load. You could do this in the models.py, for example, since you know that will be imported by Django on startup. The register function would take the app name and the url and build a dictionary which could then be passed to each template via a context processor.

Embed an optional Django application in another page, if that app is present

We have a Django project installed at multiple sites. At a couple of them, there will also be an app which produces a status box which should show on the front page, say. What's the right way to have it show up, if the app happens to be installed. If the app is not present, then nothing should display.
I think I could do it by having the status app extend the main index page:
{% extends "mainproject/index.html" %}
{% block statusboxplaceholder %}
<div> status here </div>
{% endblock %}
Is that the right, idiomatic approach? It seems slightly counterintuitive to extend the entire front page just to add a little bit of content.
EDIT: Also, how do I manage the fact that my app will want to define its own "index" page, that should be shown in preference to the project-wide "index" page? I obviously don't want to hard-code a reference to it in the project's urls.py. Do I create a deployment-specific urls.py which refers to specific apps that are installed at that deployment? And if so, isn't that repeating the information in INSTALLED_APPS, and hence violating DRY?
Although I don't see a problem with your approach, but I think a generic template tag would provide the most flexibilty, especially if you want to extend this ability to other applications that you might install later.
Your base template loads a generic "boxes" tag. In the tag's source, you can then render whatever you want based on the installed apps for that particular instance. So you can have a set of default apps to render boxes for, or the end user can customize which apps should have boxes rendered.
In your settings, configuration or even in the tag itself, you can identify the template to render for the block per app.
Assuming that each app has its templates in app/templates directory - this psuedo should get you going (this is untested):
from django.conf import settings
from django import template
register = template.Library()
class GenericBox(template.Node):
def __init__(self, app):
self.app = app
def render(self, context):
if self.app not in settings.INSTALLED_APPS:
return '' # if the app is not installed
# Here you would probably do a lookup against
# django.settings or some other lookup to find out which
# template to load based on the app.
t = template.loader.get_template('some_template.html') # (or load from var)
c = template.RequestContext(context,{'var':'value'}) # optional
return t.render(c)
#register.tag(name='custom_box', takes_context=True, context_class=RequestContext)
def box(parser, token):
parts = token.split_contents()
if len(parts) != 2:
parts[1] = 'default_app' # is the default app that will be rendered.
return GenericBox(parts[1])