I'm new to Django and Wagtail and have looked for a way to implement a "simple" like/dislike button on a blog entry page using Wagtail.
I included a total_likes IntegerField in my model for the page and would like to increment or reduce this int within the database when the user clicks a button on the html template.
The user is not supposed to be logged in. Most tutorials I've found deal with this only for registred users which is not something I want.
I would be glad if somebody could point me in the right direction. The models.py code is below.
I do not understand how to call a function from within a template.
class BlogEntry(Page):
date = models.DateField("Post date")
intro = models.CharField(max_length=250, blank=False)
body = RichTextField(blank=True)
tags = ClusterTaggableManager(through=BlogEntryTag, blank=True)
categories = ParentalManyToManyField('blog.BlogCategory', blank=False)
total_likes = models.IntegerField(blank=False, default=0)
image = models.ForeignKey(
"wagtailimages.Image",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="+"
)
streamBody = StreamField([
("text", blocks.StaticContentBlock()),
("quotes", blocks.QuoteBlock()),
("image_and_text", blocks.ImageTextBlock()),
("related_articles", blocks.RelatedArticlesBlock()),
], null=True, blank=True)
sidebarBody = StreamField([
("text", blocks.StaticContentBlock()),
("quotes", blocks.QuoteBlock()),
("image_and_text", blocks.ImageTextBlock()),
("related_articles", blocks.RelatedArticlesBlock()),
], null=True, blank=True)
search_fields = Page.search_fields + [
index.SearchField('intro'),
index.SearchField('body'),
]
content_panels = Page.content_panels + [
MultiFieldPanel([
ImageChooserPanel("image"),
FieldPanel('date'),
FieldPanel('tags'),
FieldPanel('categories', widget=forms.CheckboxSelectMultiple),
], heading="Blog information"),
FieldPanel('intro'),
StreamFieldPanel("streamBody"),
]
sidebar_panels = [
MultiFieldPanel([
FieldPanel("sidebarBody"),
], heading="Sidebar Content")
]
edit_handler = TabbedInterface(
[
ObjectList(content_panels, heading="Custom"),
ObjectList(Page.promote_panels, heading="Promote"),
ObjectList(Page.settings_panels, heading="Settings"),
ObjectList(sidebar_panels, heading="Sidebar"),
]
)
def __str__(self):
return self.total_likes
def likePost(self):
self.total_likes += 1
def dislikePost(self):
self.total_likes -= 1
Overview
Welcome to Django and Wagtail, there is a lot to learn but hopefully you are finding it fun. The first thing to wrap your head around is how a website (browser / client) can talk to the server (Python code running Django/Wagtail). Even though your Page model has a likePost method, you will need to provide a way for your Page to handle this kind of request.
The web uses a system of HTTP requests, the most common being GET and POST, where GET is used to pull down data to show to the user, POST is used for when the website wants to send something back to the server.
Django's docs on working with forms may be a good place to start to understand this process a bit more. Once your website has a form (in the HTML template), you can provide a way to 'listen' to this form when submitted. Wagtail has a method that exists on all Page models that is called serve and it allows you to override the normal behaviour.
Solution
In the solution below you will need to do the following:
1. Add two forms to your template (e.g. blog_page.html)
Remember to load the tags wagtailcore_tags so that you can access the page's URL in the form.
For simplicity, two forms have been created, one with a button for like and another with a button for dislike.
Both forms will use the method="POST" and the action (this is the URL to POST to) being the current URL.
Each form contains a csrf_token, you can read more about this in the Django docs but this helps avoid some security issues.
Each form contains a html input that is hidden with a name and value, we will use the name only in the server code to determine what button has been clicked.
{% extends "base.html" %}
{% load wagtailcore_tags %}
... BLOG CONTENT
<form action="{% pageurl page %}" method="POST" role="form">
{% csrf_token %}
<input type="hidden" name="like" value="true">
<input type="submit" value="LIKE">
</form>
<form action="{% pageurl page %}" method="POST" role="form">
{% csrf_token %}
<input type="hidden" name="dislike" value="true">
<input type="submit" value="DISLIKE">
</form>
2. Override the serve method in your Page model
The serve method on the Page model takes the argument request, which is the Django request object and should return a response. Thankfully we do not have to think about how this response is built, just know that we must call the super (original) version of this after any logic.
Check if the current request is a POST request and if so, then check what kind of value has been submitted, depending on the value call the Page method that matches
Update the likePost and dislikePost methods to ensure that the model data gets Saved via self.save()
class BlogEntry(Page):
# ...
def likePost(self):
self.total_likes += 1
self.save() ## note: you may need to ensure you call save to update data
def dislikePost(self):
self.total_likes -= 1
self.save() ## note: you may need to ensure you call save to update data
def serve(self, request):
if request.method == 'POST':
if request.POST.get('like'):
self.likePost()
# a form POST has been submitted with a value for 'like'
if request.POST.get('dislike'):
# a form POST has been submitted with a value for 'dislike'
self.dislikePost()
# ensure we call the super's serve method so that the page loads correctly
return super(BlogPage, self).serve(request)
Good to know
As you have noted, this is a basic request that does not require authentication (sign in) to submit a like, however it is very possible you will get a lot of spam this way and you may want to consider other approaches (even third party systems) to work around this.
This way of storing likes (as an integer) will also not give you much data, just a number at the current point in time, it might be worth tracking individual submissions and then providing a tally to the UI.
Here is a great overview of HTTP from MDN that I have found to be a good reference for understanding how a server handles requests & responses.
Related
I am trying to create a alert section for a menu (similar to the menu on this page https://metageeky.github.io/mega-menu/responsive-header.html)
*Each alert should have an effective date (date alert is “posted” live) and resolved date (date alert is “removed” live). Each alert will also have a maximum of one to two sentences of text describing situation.
The number of active/current alerts will appear in parenthesis following the icon and ALERT link text.
The icon and text are Dark Orange. When you hover over the icon and text, an underline appears.
When users click on the link, they are taken to a page that lists all active alerts. At bottom of page, message displays “If you are experiencing an issue, please contact us at....”
If there are no Alerts:
The number of alerts in parenthesis following the icon and link text will not appear.
Both the icon and alert text will be Primary Blue.
When Users click on the link, they are taken to a secondary alerts page that displays a message that says “There are currently no active alerts. If you are experiencing an issue, please contact us at...”
How would i achieve this?
Thank you.
There is a lot to unpack in your question but here is a high level approach.
1. Define your model
Read the Django docs on how to create a Model
Read the Django docs on what types of Fields exist
In your models.py, you will need to create a new model that has all the data you need for your requirements.
from django.db import models
class Alert(models.Model):
title = models.CharField()
description = models.TextField()
date_from = models.DateTimeField()
date_to = models.DateTimeField()
2. Ensure you can edit/manage your model data
Now you need to provide a way for your admin users to access the data model, edit & create items.
Wagtail has a great Snippets feature that allows this to work without too many changes, you will need to add #register_snippet on your model and also define some panels.
from wagtail.admin.edit_handlers import FieldPanel
from wagtail.snippets.models import register_snippet
from django.db import models
#register_snippet
class Alert(models.Model):
#... fields (defined above)
panels = [
FieldPanel('title'),
FieldPanel('description'),
FieldPanel('date_from'),
FieldPanel('date_to'),
]
def __str__(self):
return self.title
3. Prepare a template tag to show the queried data
Now you will need to work out how to query the model in a way that it will return the alerts based on your requirements (current date should be within the date range of the data).
Django has docs on writing queries
The simplest way to get the results of this query into the template will be with a custom Template Tag
An inclusion_tag is a way to have a small template fragment that can be used anywhere with custom data (without having to pass it into each View).
In the example below, you will still need to create the template file current_alerts.html which will contain how you want to render the alerts.
In your template tag template you can also use the page_url tag to provide a link to the alerts_page
# template_tags/custom_tags.py
# remember to create a template_tags/__init__.py file also
from django import template
from .models import Alert
register = template.Library()
#register.inclusion_tag('current_alerts.html')
def show_alerts():
# just returns all alerts, but this query can be refined to suit what you need
current_alerts = alerts.Objects.all()
alerts_page = AlertPage.Objects.all().first() # this assumes there will only ever be one
return {'alerts_page',alerts_page,'current_alerts': current_alerts}
4. Use your template tag & add styling
Now you need to include the tag at the top of the page inside your root/shared template.
{% extends "base.html" %}
{% load custom_tags %}
{% block body_class %}template-blogpage{% endblock %}
{% show_alerts %}
{% block content %}...{% endblock %}
5. Create a AlertsPage
You will need to create a new Page type to redirect users to within your alerts link.
https://docs.wagtail.io/en/stable/topics/pages.html
This Page can be anywhere in your tree and the Page's view template can also use the same shared template or you can pass the alerts to the view via the template context
I am using flask to deploy my model for image similarity search. To run it, I will have two pages:
1) first page (say, query.html) that shows a randomly generated image from my dataset; users can continue to refresh this page till they see an item that they like. In html, it would be something like <img src="{{ random_path }}" alt="">. When the user is satisfied, the image needs to be clicked to direct to the next page;
2) second page (say, results.html) that takes the img src from the first page, and run the model at backend and return the similar images.
I am now confused how I should link step 1) to step 2) - how to get the img src on click and pass that information on to the next step?
You can use a form with hidden input tags and make a post request (More on flask's post request here) to pass on the data from one page to the other. In code it would look something like this:
HTML:
<html>
<body>
<form action = "/imagematch" method = "post">
<p><input type = "hidden" name = "imagesrc" /></p>
<p><input type = "submit" value = "submit" /></p>
</form>
</body>
</html>
Python Backend:
#app.route('/imagematch',methods = ['GET', 'POST'])
def login():
if request.method == 'POST':
user = request.form.get('imagesrc')
Alternatively you can use JavaScript localstorage/sessionStorage. More on localstorage here:
Write a <form> and place the <input type="hidden"> (with base64 format of the image as value) or <input type="hidden"> (with the server file path as value) whichever comfortable and upon submitting the form the image path/base64 format of the image as value will be sent to server.
Or
Using Javascript or JQuery upon user selects the image, send the image to the server via a AJAX request and do whatever you want at the server-side and return the status to the user accordingly.
Option 1 is simple and straight forward and you need to deal with base64 conversion.
Option 2 is a little complex and needs good JQuery expertise.
I have 10k+ products that need their HTML Page and the stuff within is to be static (thus searchable). I am trying to find a way to do the following using django:
Loop over all the items.
Get the matching information.
Fill a model template.
Save such template with the information now static.
As much as I tried looking here on Stack Overflow and in the web, I did not find any instructions to do so.
Build a standard template that has variables for the products. Then on the backend you can search for the products you want and populate the template with their information. Something like this for a concept:
search.html
<form action="{% url 'your_url' %}" method="POST">
{% csrf_token %}
<input type=text name=input value="" />
<input type="submit" value="Submit">
product.html
<html>
<div>Hello {{name}}!</div>
</html>
models.py
class Names:
user_name = models.CharField(max_length=200)
def __str__(self):
return self.user_name
views.py
input = request.POST['input']
name = Names.objects.get(user_name=input)
return render_to_response('product.html', {'name': name})
This will allow your user to search something, pull up the record searched, and place it in the template.
You can use that system for any number of variables.
I have a contact form that appears at the bottom of every page in the footer. I simply included it in my base.html at the bottom. Now i want to figure out a way to submit it. All the examples wagtail provides is under the assumption i have an entire page dedicated to it and hence submits to itself.
This cannot work for me as its not a page.
I have written pseudo code of what I think it should look like .
def submitContact(request):
source_email = request.POST.get('email')
name = request.POST.get('name')
message = request.POST.get('message')
if (source_email is not None) and (name is not None) and (message is not None):
body = "sample"
send_mail(
name,
message,
source_email,
['test#foobar'],
fail_silently=False,
)
Then my form would be something like this
<form class="form-group" role="form" method="post" action="/submitContact">
......
</form>
Ideally if someone could point to Wagtail resources that show how to create endpoints in models that do not inherit from the Page model and are not snippets that maintain "request" content that would be useful. Ideally what I would prefer is to log this data into contact "table" then send the email after.
What should I add to my urls.py to reroute the request with the correct context for the function to retrieve the required variables and thus send the email
Additional info
I wrapped a snippet around the footer to provide some context to it using templatetags, just putting this out there incase it adds value
See below.
#register.inclusion_tag('home/menus/footer.html', takes_context=True)
def footers(context):
return {
'footers': Footers.objects.first(),
'request': context['request'],
}
You should use {% url %} template tag.
urls.py :
from django.conf.urls import url
from yourapp.views import submitContact
urlpatterns = [
url(r'^contact/$', submitContact, name='contact'),
]
Template :
<form class="form-group" role="form" method="post" action="{% url 'contact' %}">
......
</form>
Another improvement is to use Django Form.
Note : prefer lower_case_with_underscores naming style for functions. Use CamelCase for classes. See PEP8 for more information.
Instead of trying to build it yourself, why not take a look at the already existing Form Builder of Wagtail?
It enables you to create a FormPage on which you can display a custom form and even E-mail the results.
Check out the documentation here.
I'm reading a Django tutorial and in the tutorial, the urls.py is this:
(r'^vote/$', bookmark_vote_page),
and there is a model called 'SharedBookmark':
class SharedBookmark(models.Model):
bookmark = models.ForeignKey(Bookmark, unique=True)
votes = models.IntegerField(default=1)
users_voted = models.ManyToManyField(User)
but in the template, the link which leads to /vote/ is this:
{% if shared_bookmarks %}
<ul class="bookmarks">
{% for shared_bookmark in shared_bookmarks %}
<li>
[+]
The view which handles the link is this:
#login_required
def bookmark_vote_page(request):
if request.GET.has_key('id'): #if it is a GET request
try:
id = request.GET['id']
shared_bookmark = SharedBookmark.objects.get(id=id) #if the bookmark is found
shared_bookmark.votes += 1 #make a change to the 'votes' field in the DB
shared_bookmark.users_voted.add(request.user) #make a change in the 'users_voted' field in the DB
shared_bookmark.save()
As you can see, the template appends '?id=x' (where x is a number) to the end of the URL and the view uses the GET request and makes a change to the database. From what I read, I should ONLY use POST requests if I want to modify the database. Is there a way for me to send a POST request rather than a GET request Without creating an entire HTML form / submit button?
You are right, you should use a post request if you want to change data on the server.
If you're just using html, then you need to create a form and a submit button. If you are using javascript, you could add a click handler to the link, which submits the form.
Ui