I have setup a wagtail website. It works great for postings like a blog and simply add new pages.
But what if I want to add some extra functions to a page. Like showing values from my own database in a table.
Normally i use a models.py, views.py and template.py. But now I don’t see any views.py to add functions or a urls.py to redirect to an url?
Don’t know where to start!
Or is this not the meaning of a wagtail site, to customize it that way?
Thnx in advanced.
You can certainly add additional data to pages. One option is to add the additional information to the context of a page type by overriding its get_context method. For example, this page is just a place to display a bunch of links. The links and the collections they belong to are plain old Django models (managed as snippets). And then there is a page model that queries the database like this:
def get_context(self, request, *args, **kwargs):
context = super().get_context(request, *args, **kwargs)
collection_tuples = []
site = Site.find_for_request(request)
for collection in Collection.objects.filter(links__audiences=self.audience, site=site).distinct():
links = Link.objects.filter(audiences=self.audience, collections=collection, site=site)
collection_tuples.append((collection.name, links.order_by('text')))
# sort collection tuples by the collection name before sending to the template
context['collection_tuples'] = sorted(collection_tuples, key=lambda x: x[0], reverse=False)
return context
Another option is to do basically the same thing - but in a StructBlock. Then you can include the StructBlock in a StreamField on your page. Most of the Caltech site is written using blocks that can be included in one large StreamField on a page. Some of those blocks manage their own content, e.g. rich text blocks or image blocks, but others query data and render it in a block template.
To add to #cnk's excellent answer - you can absolutely use views.py and urls.py just as you would in an ordinary Django project. However, any views you define in that way will be available at a fixed URL, which means they'll be distinct from the Wagtail page system (where the URL for a page is determined by the page slug that the editor chooses within the Wagtail admin).
If you're defining URLs this way, make sure they appear above the include(wagtail_urls) route in your project's top-level urls.py.
Related
I'm trying to create a website with multiple template for all pages.
I've created a templates folder and there are 3 folders in it. each folder contains base.html, home.html, etc.
Admin can choose each template from admin panel and in my view load template like this.
class HomeView(TemplateView):
default_template = CustomTemplate.objects.first().name
template_name = default_template + '/home.html'
The problem is I have to restart server to apply admin's changes.
Is there any way to do this without restarting server?
I've also tried to enable / disable loader cache but I guess the problem is not depends to cache system.
Anything defined directly at class level will persist for the entirety of a process.
Luckily, Django's class-based views provide a series of hooks so that you can define things on a per-request basis. In this case, the method you want is get_template_names (which returns a list of templates to search for).
So:
class HomeView(TemplateView):
def get_template_names(self):
default_template = CustomTemplate.objects.first().name
return ['{}/home.html'.format(default_template)]
currently i'm trying to Integrating Wagtail with existing django project.
I'm new in wagtail, and still learning about wagtail
class BlogPage(Page):
body = RichTextField(blank=True)
categories = ParentalManyToManyField('blog.BlogCategory', blank=True)
location = models.ForeignKey('blog.Location', on_delete=models.PROTECT)
and then i register the category and the location model as snippets.
how's the best practice for build page contains of BlogPage with
certain category / location ?
and how to call that page from django's menu
or maybe where can i find documentation for integrating wagtail to existing django project
Thank you
I think you're looking for a Blog Listing Page, where you can list all your blog posts and then have blog posts based on a certain category.
You'll probably want to use a RoutablePageMixin (if you're not creating an SPA with Vue or React). A RoutablePageMixin lets you automatically create additional child pages, without having to create Wagtail child pages.
from wagtail.contrib.routable_page.models import RoutablePageMixin, route
class BlogListingPage(RoutablePageMixin, Page):
"""BlogListingPage class."""
template = 'cms/blog/blog_listing_page.html'
subpage_types = ['pages.BlogPage']
# ... other fields here
#route(r'^category/(?P<cat_slug>[-\w]*)/$', name='category_list')
def category_list(self, request, cat_slug):
"""Return posts in a certain category."""
context = self.get_context(request)
posts = BlogPage.objects.live().filter(categories__slug=cat_slug).order_by('-pub_date')
context['posts'] = posts
return render(request, 'cms/blog/blog_category_page.html', context)
Note I did not test this code, you may need to fix any errors and adjust this for your needs.
The above code will take your blog listing page (say its localhost:8000/blog/) and create a category listing page (ie. localhost:8000/blog/category/topic-slug/)
That topic-slug will be passed into the category_list() method, where you can then filter your BlogPage's based on the category(ies) it's in. It will add posts to your page, and render a different listing page, where you can customize your template.
It's been a while since I checked, but the Wagtail Bakery Demo probably has examples of this in there (and a lot of really sweet goodies).
You can read more about Wagtail Routable Pages at https://docs.wagtail.io/en/latest/reference/contrib/routablepage.html as well.
Big picture: I'd like my reverse method in get_absolute_url (see below) to return a url with a query parameter appended to it at the end, e.g. <url>?foo=bar. Further, I'd like bar to be specified by the POST request that triggered the call to get_absolute_url, either as an input to the form (but not a field represented by the model, something temporary) or as a url query parameter. I am easily able to access bar in my view using either method, but I can't seem to figure out how to access it in my model.
The motivation here is that my detail page splits up the fields from my model into different tabs using javascript (think https://www.w3schools.com/howto/howto_js_tabs.asp). When the user is updating the model, they choose which tab they want to update, and then the update template only renders the fields from the model which are related to that tab. More importantly, after the user submits the update, I want the detail page to know to open the specific tab that the user just edited.
(I understand how this works if the field is a part of the model; in get_absolute_url with parameters, the solution is pretty straightforward and involves using self.id. In my case though, bar is not a part of the model and I can't figure out how else to access it)
Some specifics: I have a model in my project called Context. I have implemented a generic DetailView and an update page for the model using a modelform called ContextForm and a generic UpdateView called ContextUpdate. Once the form is submitted, I redirect to the detail page using get_absolute_url in models.py:
def get_absolute_url(self):
return reverse("context:review",kwargs={"slug": self.slug})
My urlpatterns in urls.py looks something like:
urlpatterns = [
url(r'^(?P<slug>[-\w]+)$',views.ContextDetail.as_view(),name="review"),
url(r'^(?P<slug>[\w]+)/edit$',views.ContextUpdate.as_view(),name="edit"),
]
I am able to access this parameter in my UpdateView quite easily:
def post(self,request,**kwargs):
print (request.POST.get("bar")) #accessing input to form
print (request.GET.get("bar")) #accesssing url parameter
return super().post(request,**kwargs)
But when get_absolute_url is called inside the model, it seems I no longer have access to it.
Any suggestions for how to accomplish this? I want to use get_absolute_url (along with modelforms, generic views, etc.) so that I can follow Django conventions, but it seems like using get_absolute_url is making the functionality that I want difficult to accomplish. If the redirect to the detail view following the POST request were to happen inside my view, then I would know how to solve this (I think). Any thoughts or ideas would be greatly appreciated!
As you say, you can't access the request inside your get_absolute_url method. Therefore you should override get_success_url, from which you can access it.
def get_success_url(self):
return reverse(reverse("context:review", kwargs={"slug": self.object.slug}) + '?bar=%s' % self.request.GET.get('bar')
Or if you want to re-use get_absolute_url:
def get_success_url(self):
return self.object.get_absolute_url + '?bar=%s' % self.request.GET.get('bar')
The second option is DRYer but would break if get_absolute_url was changed to include a querystring like ?foo=foo.
I'm not talking about just custom fields to the form or static data, I'm talking about adding a section which actually has it's own code. Kind of a new entry in the fieldset but which introduces not a new field but some small reports on the users's activity.
Actually this questions stands for any model's change page. The Django docs show you how to overwrite the template for this page but what good is that without adding some python code also?
You can overrride default templates and default views.
Django has two different views and templates for admin record displaying. One for creating a new one and one for displaying an existing one and editing it. Related methods are:
Add Form Template and Add View for adding a new record
Change Form Template and Change View for displaying and changing an existing record
This is an example of how to prepare related override views (taken from Add/Change View link)
class MyModelAdmin(admin.ModelAdmin):
# A template for a very customized change view:
change_form_template = 'admin/myapp/extras/openstreetmap_change_form.html'
def get_osm_info(self):
# ...
pass
def change_view(self, request, object_id, form_url='', extra_context=None):
extra_context = extra_context or {}
extra_context['osm_data'] = self.get_osm_info()
return super(MyModelAdmin, self).change_view(request, object_id,
form_url, extra_context=extra_context)
You must check default django add and change templates from django source code (and maybe copying it as your new template and editing afterwards) to see how you can prepare your custom templates.
A final note, Never edit django template or vieew codes directly from source, since they are used by many different applications and and update to django source code might override your edit or may cause problems.
The Django admin is very extensible beyond overriding the templates.
Make sure you look at the ModelAdmin methods section in the documentation.
You can modify pretty much any behavior of the ModelAdmin.
You should also look at the custom form validation and ModelForms documentation, as a custom form for your model attached to its ModelAdmin gives you another (deeper, in most ways) level of customization.
OK, so let me give you an overview first. I have this site and in it there is a form section. When you access that section you can view or start a new project. Each project has 3-5 different forms.
My problem is that I don't want viewers to have to go through all 3-5 pages to see the relevant information they need. Instead I want to give each project a main page where all the essential data entered into the forms is shown as non-editable data. I hope this makes sense.
So I need to find a way to access all that data from the different forms for each project and to feed that data into the new page I'll be calling "Main". Each project will have a separate main page for itself.
I'm pretty much clueless as to how I should do this, so any help at all would be appreciated.
Thanks
You could try this. After that, you could:
Try creating a model for each project. This is done in "models.py" of the application modules created by django-admin
Use views to show that data to people (on your Main page)
If you've already seen all that, then:
First, you should create a view for your main page. So if you have an application my_app, my_app/views.py should be like:
def main_page_view(request, project_name):
# Your code here
pass
Then, to use this, you'd modify urls.py and add in something like:
(r'^projects/(?:<project_name>[a-zA-Z0-9]+)', 'my_app.views.main_page_view'),
Also, you'd need models, which are created in models.py, by subclassing django.models.Model
EDIT: re-reading your question, I guess you need this
Data can be passed from a view to a template through the context.
So say you create a summary view...
def summary(request, *args, **kwargs):
In that view you can query the database using the model api and pass the result of that query into the template for rendering. I'm not sure what your models look like, but say you had a model that had a title and the owner (as a ForeignKey to user)...
class Project(models.Model):
title = models.CharField(max_length=250)
user = models.ForeignKey(User)
Your model will be obviously be different. In your view you could query for all of the models that belong to the current user...
def summary(request, *args, **kwargs):
projects = Project.objects.filter(user=request.user)
Once you've gathered that, you can pass in the query to the template rendering system...
def summary(request, *args, **kwargs):
projects = Project.objects.filter(user=request.user)
render_to_response('project_summary.html', {'projects': projects }, ... )
When you pass the query to the template, you've named it projects. From within the template you can access it by this name...
<body>
<table>
{% for project in projects %}
<tr><td>{{ project.title }}</td></tr>
{% endfor %}
</table>
</body>
(Notice also how you can access a property of the model from within the template as well.)