Creating a alert functionality in django wagtail - html

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

Related

Django Admin: How to use the button to move to the selected URL?

I am very new to Django and trying to make my first project. I find it difficult to use the buttons I created to move to the selected URL.
Let's say my app is called TestForms and my models are: Patients, General, PST and ERBT. I would like to create two buttons - 'Previous' and 'Next' - which will be used to go to previous/next forms respectively. I try to do so using admin templates in django.
NOTE: I know changing built-in templates are not a very good idea, I will create new html file to extend this templates before doing changes on the server. For now I am doing it locally on my computer.
In submit_line.html I created two new buttons and they are like so:
{% if show_save_and_go_to_next_form %}<input type="submit" value="{% translate 'Next' %}" class="default" name="_gotonextform">{% endif %}
{% if show_save_and_go_to_previous_form %}<input type="submit" value="{% translate 'Previous' %}" class="default" name="_gotopreviousform">{% endif %}
This gives me two good-looking buttons on the site.
But these are just saving the results (working like 'Save' button), but not redirecting me to the next form as I would like to. When I am adding a new patient (admin/TestForms/patient/add/), after clicking on 'Next' I would like the server to save this patient and redirect me to admin/TestForms/general/add/ to be able to fullfil the next form, then save the changes and move on to admin/TestForms/PST/add/and so on.
I know I have to add the anchor, but I tried multiple times with different approaches and nothing worked. When I try to use <a href ...>, the button disappears. Also it is difficult for me to figure out how to move from one form to another and to disable the 'Previous' button on the first form and the 'Next' button on the last form.
Any suggestions how to achieve it?
The redirect needs to be done in your view, not in the template.
def your_view(request, *args, **kwargs):
# your code ...
if request.POST.get('_gotonextform'):
return redirect('admin/TestForms/general/add/')
else:
# do whatever you like if any other button was clicked
pass

Flask WTForms SelectField add placeholder or disabled option

I am working on a webpage using flask and wtforms. When opening the webpage, my selectfield should not hold any value (i.e. be blank, have placeholder saying "please choose an option" or something in that direction).
My form is defined like this in forms.py:
class Form(FlaskForm):
selectfield = SelectField('Title', choices=[])
I leave choices as an empty list because they are created from a database through the function get_choices:
# create instance of form
form = Form()
# run function to get data from db
form.selectfield.choices = get_choices()
Here it starts to get gnarly: Since the placeholder value should be empty (i.e. "") or something like "please choose" I don't want to have it in my database. So I add the value manually:
# append
form.selectfield.choices.append('Please choose')
The html part, where I render the form looks like this:
<form method="POST" action= {{ url_for('index') }}>
{{ form.csrf_token }}
{{ form.selectfield(class_="form-control", **{"onchange":"this.form.submit()"}) }}
</form>
What have I tried:
adding 'placeholder = "please choose"' here:
{{ form.selectfield(placeholder="please choose", class_="form-control", **{"onchange":"this.form.submit()"}) }}
(as suggested by Crast here: WTForms Can I add a placeholder attribute when I init a field?)
adding default="Please choose" to my Form class as suggested by Liu Yue (How do you set a default value for a WTForms SelectField?):
class Form(FlaskForm):
selectfield = SelectField('Title', choices=[], default="Please choose")
This works partly, but the Please Choose value should not be selectable which it still is.
I feel like I might be completely on a wrong path here, and maybe oversee a very simple feature. I really can't believe that such a popular feature is not available using wtforms.
I am thankful for any advice and guidance.

Creating a Blogpost Like/Dislike Button within Django/Wagtail

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.

Django Wagtail ajax contact form

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.

Django / HTML - are forms the only way to send POST requests?

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