I made a single-page site in static HTML with shiny screen-height divs, and a smooth scrolling function from a navbar. The website is expected to have a mixture of simple body text, a few images, and a card-deck. It all worked nicely and I was happy.
While I have used wagtail for very simple websites in the past, I cannot work out a way of making a single page site where the home page goes at the top followed by all the child pages in order. Is this possible with Wagtail's page models?
I've done something like this a while ago by rendering subpages of my HomePage class as section on this HomePage. There was some minor customization at various places involved (see below). Perhaps the hardest part was rewriting the page-urls of the "section pages" to point to the HomePage and the correct anchor on this HomePage (see below get_url_parts).
I've recycled the wagtail page class build-in in_menu to generate a navbar with/to the relevant sections.
While I've been trying to catch everything, I hope nothing relevant has slipped my mind...
Code
models/pages.py
class HomePage(Page):
"""
We only have one frontend page, on which we render child pages as subsections.
"""
parent_page_types = [
'wagtailcore.Page',
]
# The following page types (here named "sections") are standard wagtail
# page models, but rendered on this HomePage.
subpage_types = [
'TextSection',
'WhereSection',
'ContactSection',
...
]
# fields...
# panels...
def get_subsections(self):
"""
Return page queryset with sections to render on the HomePage.
"""
return (
self
.get_children()
.specific()
.live()
.public()
)
def get_context(self, request):
context = super().get_context(request)
context['subsections'] = self.get_subsections()
context['nav_sections'] = self.get_subsections().in_menu()
return context
models/sections.py
class BaseSection(models.Model):
"""
BaseSection abstract base class. All HomePage sections should inherit
from this class.
"""
parent_page_types = ['HomePage']
subpage_types = []
fields...
panels...
class Meta:
abstract = True
def get_url_parts(self, request=None, *args, **kwargs):
"""
Customising URL patterns for a page model
http://docs.wagtail.io/en/latest/topics/pages.html#customising-url-patterns-for-a-page-model
Rewrite page path to corresponding anchor of this section on the
containing page.
"""
url_parts = super().get_url_parts(request=request)
if url_parts is None:
return None
else:
site_id, root_url, page_path = url_parts
_cust_page_path = '#section-{}'.format(page_path.replace('/', ''))
return (site_id, root_url, _cust_page_path)
class TextSection(BaseSection, Page):
template = 'sections/text_section.html'
body = RichTextField()
content_panels = BaseSection.content_panels + [
FieldPanel('body'),
]
class FooSection(BaseSection, Page):
...
The rest is done via the template layer: looping over all subsection on the homepage:
# templates/home_page.html
{% extends 'base.html' %}
{% block navbar %}
{% include 'includes/navbar.html' %}
{% endblock navbar %}
{% block page_content %}
{% for subsection in subsections.all %}
{% with subsection.specific as subsection %}
{% include subsection.template with subsection=subsection %}
{% endwith %}
{% endfor %}
{% endblock %}
# templates/includes/navbar.html
{% load wagtailroutablepage_tags %}
<nav>
{% for item in nav_sections %}
<a
href="{% if not is_homepage %}{% routablepageurl page 'homepage' %}{% endif %}#section-{{ item.slug }}"
>
{{ item.title }}
</a>
{% endfor %}
</nav>
# templates/sections/section_base.html
<section id="section-{{ subsection.slug }}" class="{{ subsection.content_type|slugify }}">
{{ subsection.title }}
{% block content %}
{% endblock content %}
</section>
# templates/sections/text_section.html
{% extends 'sections/section_base.html' %}
{% block content %}
{{ subsection.body|richtext }}
{% endblock content %}
If you're building one page using Django templates, you should be able to pass a list of pages to your template using the get_context() method.
If it's an SPA, you could use AJAX requests to fetch other pages data from the built-in Wagtail API. If it doesn't fully suit your needs, you could still build your own API with e.g. the Django Rest Framework.
Related
I am trying to implement django-autocomplete-light in my projects but cannot figure out why it does not show me the autocomplete widget, but keeps showing an empty dropdown.
I followed the tutorial: https://django-autocomplete-light.readthedocs.io/en/3.1.3/tutorial.html.
I found that this problem has occurred in other stackoverflow questions, but none of those answers have helped me so far.
I have the following model:
class Vilt(models.Model):
vilt_title = models.CharField(max_length=200, unique=True)
I created this autocomplete view:
class ViltAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
# Don't forget to filter out results depending on the visitor !
# if not self.request.user.is_authenticated():
# return Vilt.objects.none()
qs = Vilt.objects.all().order_by('vilt_title')
if self.q:
qs = qs.filter(vilt_title__istartswith=self.q)
return qs
I use this ModelForm where I specify the widget.
from .models import Vilt
from dal import autocomplete
class ViltSearchForm(forms.ModelForm):
vilt_title = forms.ModelChoiceField(
queryset = Vilt.objects.all(),
widget = autocomplete.ModelSelect2(url='vilt-autocomplete')
)
class Meta:
model = Vilt
fields = ('vilt_title',)
from .views import (ViltAutocomplete,
)
urlpatterns = [
#other paths
path('vilt/autocomplete/', ViltAutocomplete.as_view(), name='vilt-autocomplete'),
#other paths
]
{% extends "bierviltje/base.html" %}
{% load static %}
{% load crispy_forms_tags %}
{% block content %}
<div class="container">
#other forms
<div>
<form action="" method="post">
{% csrf_token %}
{{ vilt_search_form|crispy }}
<input type="submit" />
</form>
</div>
#other forms
</div>
{% endblock content %}
{% block javascript %}
{{ vilt_search_form.media }}
{% endblock javascript %}
This is the Javascript that is loaded in before the javascript block in base.html:
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
{% block javascript %}
{% endblock javascript %}
It's been sometime since you posted your query. But in case you have not found the answer yet, here is the solution:
In your ModelForm "ViltSearchForm", please change the widget from:
widget = autocomplete.ModelSelect2(url='vilt-autocomplete')
to:
widget = autocomplete.ListSelect2(url='vilt-autocomplete')
However, if I may add here, I can't fathom the reason for using "autocomplete" on a stand alone model.
A feedback would be much appreciated.
I’m a bit stuck on how to display the titles for 2 pages that list articles of different categories using the same template. My article models have content, category (sport, politics, tech etc) and status. I want to display a page listing all sport articles and a page listing politics and I want to use the same template. I have tried storing the title as a variable in the view but this doesn't work
Views.py
def sport_landing(request):
page_title = 'Sport'
cat_landing = Article.objects.filter(status='published', category='sport', published_date__lte=timezone.now()
).order_by('-published_date')
return render(request, "categorylanding.html", {'cat_landing': cat_landing}
Template
{% extends 'base.html' %}
{% load humanize %}
{% block content %}
<div class="row category-landing-title">
<div class="col-xs-12">
{% page_title %}
</div>
</div>
.
.
.
Is this even the best way to do this?
You need to pass the variable to the context :
def sport_landing(request):
page_title = 'Sport'
cat_landing = Article.objects.filter(status='published', category='sport', published_date__lte=timezone.now()
).order_by('-published_date')
return render(request, "categorylanding.html", {'cat_landing': cat_landing, 'page_title':page_title }
and use double braces { in the template ({% %} is used for template tags):
{{page_title}}
To answer about the whole pattern, you can avoid to repeat the code for each category by using a parameter inside the url pattern :
Add and adapt this line in the file urls.py, this will allow you to pass the category as a parameter to your view :
url(r'^category/(?P<category>\w+)', views.cat_landing) # i.e : mysite/category/sport
You need to declare a generic view like :
def cat_landing(request, category=None):
if category:
cat_landing = Article.objects.filter(status='published', category=category,
published_date__lte=timezone.now()
).order_by('-published_date')
return render(request, "categorylanding.html",
{'cat_landing': cat_landing, 'page_title':category.title() }
else:
return [...redirect to main menu... ]
Pass title as context
def sport_landing(request):
page_title = 'Sport'
cat_landing=Article.objects.filter(tatus='published',category='sport',published_date__lte=timezone.now()
).order_by('-published_date')
return render(request, "categorylanding.html", {'cat_landing': cat_landing,'page_title':page_title}
{% extends 'base.html' %}
{% load humanize %}
{% block content %}
<div class="row category-landing-title">
<div class="col-xs-12">
{{ page_title }}
</div>
</div>
I'm practicing a simple blog site, where if I clicked in author names, it shows the associated post! If I used two different view function the output is fine but if I implemented in one function, in author's post page it shows a url which should not! The screenshot of the html page:
The Url Link contain author post page link! but according to the code it should display the post link!
Url Link
I'm pasting my all code for better understanding. Thanks in advance!
This is my urls.py:
urlpatterns = [
url('^$', views.url_list, name='url_list'),
url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<post>[-\w]+)/$',
views.post_details, name = 'post_links'),
# url(r'^author/(?P<author_slug>[-\w]+)/$', views.author_posts, name = 'url_list_by_author'),
url(r'^author/(?P<author_slug>[-\w]+)/$', views.post_details, name = 'url_list_by_author'),
]
and my view.py is:
def post_details(request, year=None, month=None, day=None, post=None, author_slug=None):
author_post_list = None
if year:
post = get_object_or_404(UrlPost, slug=post,
status='published',
publish__year=year,
publish__month=month,
publish__day=day,
)
if author_slug:
author_post_list = UrlPost.author_manager.author_post(author_slug)
post = None
return render(request, 'detail.html', {'post': post, 'author_post_list': author_post_list})
and html page:
{% extends "base.html" %}
{% block title %}{{post.title}}{% endblock %}
{% block content %}
<h1>{{ post.title|title }}</h1>
<p>{{ post.publish }}</p>
<ul>
{% for author in post.author.all %}
{% if forloop.first %}
<li><h4>Author: </h4></li>
{% endif %}
<li><h4>{{ author|title }}</h4></li>
{% if not forloop.last %}
<li><h4>, </h4></li>
{% endif %}
{% endfor %}
</ul>
<p>{{ post.description }}</p>
Url Link
{% include "author_post.html" %}
{% endblock %}
The first 'url link' you see at the top comes from this line in your html template:
Url Link
All the rest of what is displayed comes from the author_post.html template. The url link is not "additional", actually it's just that everything before is missing.
Probably because the post variable is None. If you actually inspect the generated html, it will look like this:
<h1></h1>
<p></p>
<ul>
</ul>
<p></p>
Url Link
(and the here you will have the contents of author_post)
OK, now for how to solve this: you should not use the same view to display the post and the post author's post. You should create two separate views. Each with its template. Then, assuming you named the new view author_posts in your urls.py:
urlpatterns = [
url('^$', views.url_list, name='url_list'),
url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<post>[-\w]+)/$',
views.post_details, name = 'post_links'),
url(r'^author/(?P<author_slug>[-\w]+)/$', views.author_posts, name = 'url_list_by_author'),
]
I have a table in my MySQL database named mysite_categories, there are 4 columns but for my purposes I just need two (name, base_url).
I currently have a template '*base_categories.html*' that I use to load the categories manually.
base_categories.html (trimmed down)
{% block content %}
<div class="section" style="float: right;">
<h4 class="gradient">Category List</h4>
<ul>
<li>Art</li>
<li>Biography</li>
<li>Science</li>
</ul>
</div>
{% endblock %}
What I'd like to do is pull the data from the db and use it in a for loop. Something like:
{% block content %}
<div class="section" style="float: right;">
<h4 class="gradient">Category List</h4>
<ul>
{% for category in mysite_categories %}
<li>{{ category.name }}</li>
{% endfor %}
</ul>
</div>
{% endblock %}
This is probably a newbie question but is it possible to something like this without creating an app?
*EDIT 1*
These are my app files, I'll admit this is probably junk, I have tried so many edits from so many different posts I'm sure I've broke it somewhere :P. I was going to remove it and start fresh but I figure I might as well post it to see where I might have gone wrong?
views.py
from django.shortcuts import render_to_response
from categories.models import categoryList
def index(request):
categories = categoryList.objects.all()
extra_context = {"categories": categories}
return render_to_response("myapp/index.html", extra_context)
models.py
from django.db import models
class categoryList(models.Model):
#id = models.IntegerField(unique=True, db_column='ID') # Field name made lowercase.
name = models.CharField(max_length=255L, unique=True)
base_url = models.CharField(max_length=255L, unique=True)
thumb = models.CharField(max_length=1L, unique=True, blank=True)
class Meta:
db_table = 'mysite_categories'
index.html
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
<div class="section" style="float: right;">
<h4 class="gradient">Category List</h4>
<ul>
{% for category in categories %}
<li>{{ category.title }}</li>
{% endfor %}
</ul>
</div>
As mentioned, it's probably junk at this point, if any of you can help me straighten this out it would be appreciated!
*EDIT 2*
base_right_panel.html
{% block content %}
<div style="float: right;">
<div id="base_categories" style="margin: 10px; padding-bottom: 10px;">
{% block base_categories %}
{% include "base_categories.html" %}
{% endblock %}
</div>
</div>
{% endblock %}
*Edit 3*
base_categories.html
{% block content %}
<div class="section" style="float: right;">
<h4 class="gradient">Category List</h4>
<ul>
{% if categories %}
{% for category in categories %}
<li>{{ category.title }}</li>
{% endfor %}
{% else %}
<p>no data! {{ categories|length }}</p>
{% endif %}
</ul>
</div>
{% endblock %}
*EDIT 4*
(Application name was changed to CategoryList)
CategoryList/views.py
from django.views.generic import TemplateView
from CategoryList.models import CategorylistCategorylist #<-- Changed to match inspectdb result
class IndexView(TemplateView):
template_name="categorylist.html" #<-- Changed name from index.html for clarity
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
context["categories"] = CategorylistCategorylist.objects.all()
return context
CategoryList/models.py
from django.db import models
class CategorylistCategorylist(models.Model): #<-- Changed to match inspectdb
id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=255L, unique=True)
base_url = models.CharField(max_length=255L, unique=True)
thumb = models.ImageField(upload_to="dummy", blank=True) #<-- Ignored inspectdb's suggestion for CharField
def __unicode__(self):
return self.name
# Re-added Meta to match inspectdb
class Meta:
db_table = 'categorylist_categorylist'
CategoryList/urls.py
from django.conf.urls.defaults import patterns, url, include
from django.contrib import admin
from django.conf import settings
from CategoryList import views
admin.autodiscover()
urlpatterns = patterns('',
url(r'^$', views.IndexView.as_view(), name='categorylist'),
)
if settings.DEBUG:
urlpatterns = patterns('',
url(r'^media/(?P<path>.*)$', 'django.views.static.serve',
{'document_root': settings.MEDIA_ROOT, 'show_indexes': True}),
url(r'', include('django.contrib.staticfiles.urls')),
) + urlpatterns
MySite/urls.py
from django.conf.urls import patterns, include, url
from django.contrib import admin
from django.conf import settings
from home import views as home_view
from CategoryList import views as index_view
admin.autodiscover()
urlpatterns = patterns('',
url(r'^$', home_view.HomeView.as_view(), name="home"),
url(r'^categories/$', index_view.IndexView.as_view(), name='categorylist'),#include('CategoryList.urls')),
url(r'^admin/', include(admin.site.urls)),
#url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
)
if settings.DEBUG:
urlpatterns = patterns('',
url(r'^media/(?P<path>.*)$', 'django.views.static.serve',
{'document_root': settings.MEDIA_ROOT, 'show_indexes': True}),
url(r'', include('django.contrib.staticfiles.urls')),
) + urlpatterns
So far I can load the url "localhost:8000/categories" and I will see the list of category names appear on the right side of the screen as expected, but there is no template formatting applied. Inside my "*base_right_panel.html*" file I've tried "{% include "categorylist.html %}" to link directly to the application, which displays the correct template formatting, but displays the "{% else %}" response from "{% if categories %}" instead of the categories? I have tried changing the include to point to "categories/", which works in the browser, but it tells me it cannot find the template?
I'm sooo stumped right now..?
This answer is not meant to disagree with Alasdair's - it's just to add some more information on working with templates.
The core handler of an HTTP request to a Django application is the view. The view receives the HTTP request, as well as any arguments captured from the URL, and is responsible for returning an HttpResponse instance (or an instance of one of its subclasses) which will be returned to the browser.
The view is not bound to use any particular method to create the HttpResponse. Rendering a template to include information derived from the database and from request information or URL arguments is sufficiently common that there's code to support it, like the render shortcut or its mostly obsolete antecedent render_to_response, but this is by no means required. It's perfectly legitimate to have a view directly construct the HttpResponse:
def index(request):
return HttpResponse('This is an index page.')
Or, for very simple HTML:
def index(request):
return HttpResponse('<html><head><title>example</title></head><body>This is an index page.</body></html>')
In practice, I have often created HttpResponse instances directly to return json data or a dynamically created PDF or Excel file.
A simple way to slot information retrieved from the database into your response would be to use Python's built-in string interpolation:
def index(request):
return HttpResponse('Hello, %s' % request.user.email)
Or you could use the advanced string formatting options:
def index(request):
user_names = {}
user_names['first_name'] = request.user.first_name
user_names['last_name'] = request.user.last_name
return HttpResponse('Hello, %(first_name)s %(last_name)s' % user_names)
All this is building up to the point that it doesn't matter how you generate the text contents of your HttpResponse. All that matters is that you return one.
The template system is a powerful and extensible tool for generating text content, but that's all it does. If you look at the template docs about rendering a template, you'll see some examples that are almost exactly the same as the the string interpolation above.
render_to_response was a shortcut that would accept a template and a context and return an HttpResponse with the rendered contents of that template and context. Skipping over its context_instance and content_type parameters for the sake of demonstration, these two code blocks are identical in effect:
def index(request):
t = Template('Hello, {{ first_name }} {{ last_name }}')
c = Context({'first_name': request.user.first_name, 'last_name': request.user.last_name})
response_text = t.render(c)
return HttpResponse(response_text)
Assume a template index.txt exists as defined below, at the top level of an entry in the setting's TEMPLATE_DIRS tuple.
index.txt
Hello, {{ first_name}} {{ last_name}}
Then the view above could be replaced with:
def index(request):
t = get_template('index.html')
c = Context({'first_name': request.user.first_name, 'last_name': request.user.last_name})
response_text = t.render(c)
return HttpResponse(response_text)
Alternatively, you can skip the explicit creation of the context object and rendering of the template into a string thus:
def index(request):
return render_to_response('index.html', {'first_name': request.user.first_name, 'last_name': request.user.last_name})
On more recent versions of Django you should generally use the render shortcut rather than render_to_response - the details are a bit too much to go into if you're still struggling with getting context into your templates.
def index(request):
return render('index.html', {'first_name': request.user.first_name, 'last_name': request.user.last_name})
Of course, part of what makes templates useful is that the rendering engine can perform certain kinds of logic and lookup. I don't actually need to explictly keep looking up first_name and last_name - I can just pass in request as part of my context and look up its attributes in the templates:
index_showing_context.html
Hello, {{ request.user.first_name }} {{ request.user.last_name }}
def index_showing_context(request):
return render('index_showing_context.html', {'request': request})
Even passing in request isn't strictly necessary in that example, because one of the differences between render and render_to_response that I alluded to above is that request is always part of the context for render. But, again, that's an advanced subject.
So for your particular problem, it really doesn't matter where in your templates you render the data you want, as long as you have provided it to your view's context and are rendering the correct template. The template is in effect just a file name used to find and build a string, into which your context will be interpolated.
The {% include %} template tag is one way to mix template fragments into other templates. If I wanted to, I could set mine up like this:
header.html:
<head>
<title>This is a sample title.</title>
</head>
index.html:
<html>
{% include "header.html" %}
<body><p>This is my template body, {{ request.user.first_name }} {{ request.user.last_name }}.</p></body>
</html>
detail.html:
<html>
{% include "header.html" %}
<body><p>This is a detail page, probably for something selected in the context and given the context key 'object'.</p>
<p>{{ object }}</p>
</body>
</html>
That works fine, but it's not the only option. From your question, I see that you're using blocks and template inheritance. A common idiom is to define a base template that all or almost all other templates will inherit from:
base.html
<html>
<head>
<title>{% block title %}Default title{% endblock %}</title>
{% block extra_head_elements %}{% endblock %}
</title>
<body>
{% block body_header %}Standard page header here {% endblock %}
{% block body_content %}{% endblock %}
{% block body_footer %}Standard page footer here {% endblock %}
</body>
</html>
index.html
{% extends "base.html" %}
{% block title %}index {% endblock %}
{% block body_content %}<p>This is my template body, {{ request.user.first_name }} {{ request.user.last_name }}.</p>{% endblock %}
detail.html
{% extends "base.html" %}
{% block title %}detail{% endblock %}
{% block body_content %}<p>This is a detail page, probably for something selected in the context and given the context key 'object'.</p>
<p>{{ object }}</p>
{% endblock %}
So ultimately, I am not quite sure how you should best stitch together your right panel concept because it depends on the way you want your pages to work. If it's going to be present everywhere or almost everywhere, I would recommend putting it into a base template that the rest of your templates will extend. If you want it on exactly one page, just literally include it in that template. If you want it on some but not all pages, a template fragment that you can {% include %} is probably best.
The main thing is to understand how the template engine will compose your {% include %} and {% extends %} tags, and to provide the necessary data to the template's context in your view.
Edit:
If I wanted to have a view and template pair that just retrieved the categories, this is a simple way to lay it out using your example model code and rendering. There are other options.
index.html
<html>
<head><title>Simple category listing</title></head>
<body><p>The categories are:</p>
<ul>
{% for category in categories %}
<li>{{ category.name }}</li>
{% endfor %}
</ul>
</body>
</html>
view:
def index(request):
categories = categoryList.objects.all()
extra_context = {"categories": categories}
return render_to_response("index.html", extra_context)
If I wanted to reuse the category listing on multiple pages, that gets back into the include vs. extends discussion above. Either way, the template will always require your view to pass in categories as a context variable.
You could execute custom SQL directly to fetch categories in your view, and loop through the output in your template. This would not require an app.
If you create a model, you will be able to use the Django queryset api, which is very convenient, e.g.
mysite_categories = Category.objects.all()
This does require you to create an app. However, creating an app is really easy, just use the startapp command.
python manage.py startapp myapp
Once you've created your app, you can use the inspectdb command to inspect your database, and create a model for your mysite_categories table.
I need a subnavigation on my pages. I inherit from base.html where the main navigation is located, but I don't know how to make a subnavigation which differs from page to page.
I've thought about making a template tag in which I can specify items to the subnavigation in each template file, and only output the subnavigation if any subnavigation items are specified. How do others do it?
Why can't you have a separate block for subnavigation and override that block in child templates?
base.html
Calls
Messages
{% block subnav %}
{% endblock %}
calls.html
{% extends "base.html" %}
{% block subnav %}
Outbound calls
Inbound calls
{% endblock %}
messages.html
{% extends "base.html" %}
{% block subnav %}
Sent messages
Recieved messages
{% endblock %}
A simple way to implement such could be to create a templatetag that takes an argument, for example the unique id or slug of the current page in the page tree.
E.g.
#register.simple_tag
def subnavigation_for_page(request, page_id, *args, **kwargs):
qs = Page.objects.filter(is_active=True)
current_page = qs.get(id=page_id)
sub_navigation = list()
for page in qs.filter(level__gt=current_page.level):
if page.level > current_page.level:
sub_navigation.append(page)
return sub_navigation
You could extend the example to return the rendered navigation as html or use the 'as' node to return a variable and define the html within the template itself.
Package that might be of interest:
django-mptt