Django & HTMX - after GET show only section of html template - django

I want to create an instagram clone. I created a post.html which includes a post-card.html file for all posts and a post-filter.html to filter the posts.
<!-- simple post.html view -->
<div>
<div>
{% include '_includes/bars/post-filter.html' %}
</div>
<div id="more-posts-wrapper">
{% for post in posts %}
{% include '_includes/cards/post-card.html' %}
{% endfor %}
</div>
</div>
Now I added htmx to load more posts after clicking load more button. I tried to load the new posts after the existing one inside the div with id=more-posts-wrapper:
<div>
<div>
{% include '_includes/bars/post-filter.html' %}
</div>
<div hx-get="?page={{ page_obj.next_page_number }}"
hx-trigger="click"
hx-swap="innerHTML"
hx-target="#more-posts-wrapper">
<p>LOAD MORE</p>
</div>
<div id="more-posts-wrapper">
{% for post in posts %}
{% include '_includes/cards/post-card.html' %}
{% endfor %}
</div>
</div>
Unfortunately, if I press the button, the correct reponse post gets delivered but the whole post.html document gets loaded after the div with id=more-posts-wrapper. I only want to load the post-card.html file and not reload the post-filter.html file.
My views.py
class MemesView(ListView):
model = Post
template_name = 'memes.html'
context_object_name = 'posts'
paginate_by = 1
Does anyone know what I can do?

With HTMX we have two types of request:
Full page: when user loads the page we want to show the full template with the filters and the content as well.
Partial page: after the first load, we want to load only the content part via HTMX and update the corresponding part of the page.
We have to structure our templates accordingly, so we can reuse them in these two request.
The full page's template post_page.html:
<div>
<div>
{% include '_includes/bars/post-filter.html' %}
</div>
<div id="more-posts-wrapper">
{% include 'posts_partial.html' %}
</div>
</div>
And the posts_partial.html partial template where we put everything we want to load via HTMX:
<div hx-get="?page={{ page_obj.next_page_number }}"
hx-trigger="click"
hx-swap="innerHTML"
hx-push-url="true"
hx-target="#more-posts-wrapper">
<p>LOAD MORE</p>
</div>
<div id="more-posts-wrapper">
{% for post in posts %}
{% include '_includes/cards/post-card.html' %}
{% endfor %}
</div>
In this template we have the post cards for the selected page, furthermore we have to place our 'Load more' button here as well, because we have to update the page_obj.next_page_number value after each request at the button.
In the view function we can check for HX-Request: true header in order to detect a HTMX request. If the request was made by HTMX, we set the partial template in the get_template_names() method, otherwise we use the full page template.
class MemesView(ListView):
model = Post
context_object_name = 'posts'
paginate_by = 1
def get_template_names(self):
is_htmx = self.request.headers.get('HX-Request') == 'true'
return 'posts_partial.html' if is_htmx else 'post_page.html'
Furthermore I added the hx-push-url="true" so HTMX will update the page number in the URL, so the user can refresh and receive the correct page after a few HTMX request.

Related

Getting ValuError: Field 'id' expected a number but got a html

I am a newcomer, about 10 days into a self-taught Django, and I am building a Django (v3.2) app.
The main view in the app is a list of countries (view: CountryListView, template: countries_view.html).
When I click on a country in that list, a detail view is brought up to see detailed country data (CountryDetailView and country_detail.html).
In CountryDetailView, there are navigation buttons: either back to CountryListView, or 'Edit', which shall bring the user to CountryEditView, where the country parameters can be changed & saved.
However, when I click on 'Edit', I get the following error:
Request Method: GET
Request URL: http://127.0.0.1:8000/manage_countries/countries/country_edit.html/
Django Version: 3.2.4
Exception Type: ValueError
Exception Value: Field 'id' expected a number but got 'country_edit.html'
I am guessing this might to do something with values returned (or rather expected but not returned) from CountryDetailView, but what they are? And how to make CountryDetailView to return the object id? (I am using plain integer id's in my model)
views.py
class CountryListView(LoginRequiredMixin, ListView):
model = Countries
context_object_name = 'countries_list'
template_name = 'manage_countries/countries_view.html'
class CountryDetailView(LoginRequiredMixin, DetailView):
model = Countries
template_name = 'manage_countries/country_detail.html'
class CountryEditView(LoginRequiredMixin, UpdateView):
model = Countries
template_name = 'manage_countries/country_edit.html'
success_url = reverse_lazy('manage_countries:countries_view')
urls.py
path('', CountryListView.as_view(),name='countries_view'),
path('countries/<pk>/', CountryDetailView.as_view(), name='country-detail'),
path('<pk>/edit', CountryEditView.as_view(), name='country_edit'),
countries_view.html
{% block content %}
<div class="list-group col-6">
Click here to add country data
{% for country in countries_list %}
<small><span class="text-dark">{{ country.name }}</span></small>
{% endfor %}
</div>
{% endblock content %}
country_detail.html, with two navigation buttons (back to the list), and further to Edit form (this is the one that does not work).
{% block content %}
<div class="card col-5 shadow-mt">
<h5 class="card-header bg-light text-center">Country data</h5>
<div class="card-body">
<table class="table">
<thead><tr>
<th scope="col"><small>Name: </small></th>
<th scope="col"><small>{{ object.name }}</small></th>
</tr></thead>
</table>
<button class="btn btn-secondary mt-3" onclick="javascript:history.back();">Back</button>
<button class="btn btn-secondary mt-3" onclick="window.location.href='../country_edit.html';">Edit</button>
</div>
</div>
{% endblock content %}
Your onclick attribute of button contains invalid url:
<button>onclick="window.location.href='../country_edit.html';">Edit</button>
Use instead template tag url (Django Docs):
<button class="btn btn-secondary mt-3" onclick="window.location.href='{% url 'country_edit' object.pk %}';">Edit</button>

In django, creating a randomized object list with paging

I've been trying to get a list of model objects, randomize their order and paginate them in the template. I thought I've done it until I realize that in each next page call I had been re-randomizing objects rather than using the previously randomized list.
Is there anyway I can randomize the object list only while opening the page first time and after that use the same list without randomizing it while going to the next page?
Thanks.
Views.py
class index(View):
def get(self, request):
all_items = list(Electronics.objects.all())
random.shuffle(all_items)
paginator = Paginator(items, 24)
page = request.GET.get('page')
items = paginator.get_page(page)
return render(request, 'home.html', {'items':items, 'header':'Homepage'})
home.html
<div class='container'>
<div class='row'>
{% for item in items %}
<div class='col-xs-6 col-sm-8 col-lg-4'>
<img src='{{ item.image.url|cut:"%22"|thumb}}'>
<h4>{{item.name}}</h4>
<p>$ {{item.price}}</p>
<form method='get'>
<input value='Add to cart' type='submit' formaction= "{% url 'addtocart' item.id %}">
<input value='View Details' type='submit' formaction= "{% url 'detail' item.id %}">
</form>
<div><br><br>
{% endfor %}
</div>
</div>
<div class='pagination' align='center'>
<span class='step-links'>
{% if items.has_previous %}
« first
previous
{% endif %}
<span class="current">
Page {{items.number}} of {{items.paginator.num_pages}}
</span>
{% if items.has_next %}
next
last »
{% endif %}
</span>
</div>
You can use this great snippet to implement a randomized seeded queryset mixin. This will basically allow you to create a GET request to the view using the mixin (via a url such as /electronics?page=1), and then it will generate a seed which will be cached in your session, and then reused to pull iterative sets of records.
This shouldn't be too much of a burden on your load, since caching a seed is inexpensive - it is the use of order_by('?') which can be expensive, but 3-4 thousand records isn't considered that big for such a task.

A collapsible (blog) type menu in django

I am 99% certain that I did not use the correct name but I am trying to find information on how to create a menu that looks and behaves similar to a blog archive menu (just the correct name would already help me look in the right place). Specifically, the ability to click on a top level field and then get all it's children, see the following example:
My intent is to create such a menu but then using fields from my database where an imaginary entry would be as follows:
class foo(models.Model):
a = models.CharField(max_length=200)
b = models.CharField(max_length=200)
c = models.CharField(max_length=200)
The menu would than show all unique values of a as a top level (like the year in the example) and clicking on it would show all unique b values (like month in the example), clicking that would list all individual corresponding entries.
I would prefer the correct name for such a menu and if possible some documentation/guide on how a similar concept would be created using django.
I have managed to get this working using some bootstrap and django.
The view was generated so that it was a list of querysets as follows (with the enzyme_activity_type=types.values()[0] being the only bit that I don't like:
class IndexView(generic.ListView):
template_name = 'gts/index.html'
context_object_name = 'latest_enzyme_list'
def get_queryset(self):
try:
resultList = []
for types in Enzymes.objects.values('enzyme_activity_type').distinct():
resultList.append(Enzymes.objects.filter(enzyme_activity_type=types.values()[0]))
return resultList
except:
return Enzymes.objects.none()
The html template using the queryset and bootstrap (the relevant bits for bootstrap and javascript are in the base.html):
{% extends 'base.html' %}
{% block body %}
<div class="section" id="s-module-django.db.models.fields">
<h1 class="page-header text-primary">Glycoenzymes for Bioindustries</h1>
<div>
<p>Complete list of all enzymes in the database:</p>
</div>
{% if latest_enzyme_list %}
<ul>
{% for enzyme_type in latest_enzyme_list %}
<div class="panel-heading">
<button type="button" class="btn btn-info" data-toggle="collapse" href="#{{enzyme_type.first.enzyme_activity_type|cut:" "}}">
{{enzyme_type.first.enzyme_activity_type}}
</button>
</div>
<div id="{{enzyme_type.first.enzyme_activity_type|cut:" "}}" class="panel-collapse collapse">
{% for enzyme in enzyme_type %}
<div><li>{{ enzyme.barcode }}</li></div>
{% endfor %}
</div>
{% endfor %}
</ul>
{% else %}
<p>No enzymes are available.</p>
{% endif %}
</div>
{% endblock %}
Excerpt from the base.html (to illustrate the bootstrap inclusion):
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}"/>
</head>
<body>
{% block body %}
{% endblock %}
<!------- Bootstrap + JQuery JS--------->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="{% static 'scripts/bootstrap.min.js' %}"></script>
</body>
</html>
This results in the following picture (with the first item 'activated'):
Please post a comment if the way I fixed it should be altered in any way to be correct Django programming.

How can I allow user to download or view a PDF online that I am hosting in my static folder?

I have a modal that stores the file_name, file_link, is_active and an uploaded on field. I can add the PDF via the admin site but I am trying to allow the users the ability to download the PDF or view the PDF on a new tab from the front end webpage that I created.
Currently, the view that I created is getting the information for each PDF so that I can display the name, link, etc. If I put the {{ pdf.file_link }} into an it doesn't do anything.
I would really appreciate getting some assistance on this so that I can stope spinning my wheels. Thank you in advance!+
EDIT:
Models:
class HelpfulPDF(models.Model):
file_name = models.CharField(max_length=256)
file_link = models.FileField(upload_to='pdfs')
uploaded_on = models.DateField(auto_now=True)
is_active = models.BooleanField(default=True)
def __str__(self):
return "{} - {}".format(self.id, self.file_name)
View:
As you can see in the view, I am only getting the date from the model. I am not doing anything special with the actual PDF yet. That is the question.
#login_required()
def help_center_view(request):
data = dict()
data['userprofile'] = request.user.userProfile
data['get_pdfs'] = HelpfulPDF.objects.filter(is_active=True)
return render(request, 'help_center.html', data.items())
Template:
{% extends 'base.html' %}
{% load staticfiles %}
{% block content %}
<hr>
<div class="text-center">
<b>Please look at the below PDF's.</b> <i>(You can view in browser or download to your computer)</i>
</div>
<hr>
<br><br>
<div class="text-center">
{% if get_pdfs %}
{% for each_pdf in get_pdfs %}
<div style="display: inline-block; width:275px; height: auto; border: thin grey solid; border-radius: 15px ">
<br>
{% if each_pdf.file_name %}
<h6>{{ each_pdf.file_name }}</h6>
{% else %}
<h6>Helpful PDF</h6>
{% endif %}
<hr>
<div style="margin-top: 13em">
<hr>
<button class="btn-primary btn-sm">Download PDF</button>
<button class="btn-primary btn-sm">View PDF Online</button>
</div>
<br>
</div>
{% endfor %}
{% else %}
<h4>There are no PDF's to display at this time.</h4>
{% endif %}
</div>
{% endblock %}
If your link to the pdf file works (try going to it in your browser and see what happens), then it should be possible to do the equivalent of this in your template:
<a href={{ your_link }}></a>
Add the target="_blank" attribute to have it open in another tab.
You can add a download attribute to the anchor element if you want it to download on click.
The browser should handle opening or downloading the PDF automatically. If not, then either your link is not valid (the most likely cause) or (much less likely because you'd probably know it) your server is sending the wrong content-type header in the response (which you can inspect from the browser dev console in the Network tab on Chrome).
You can create a view to proxy the PDF files and return specific headers, see the following answer: How to show a PDF file in a Django view

Creating Django Wagtail Sidebar with Index Page Child Links

I'm building a site using Django Wagtail and am having trouble figuring out how to add a sidebar menu that will list all the child pages of the parent index page. For example, I have a standard_index_page.html that I created a parent page with in Admin, then I added child pages of that using standard_page.html template.
In my standard_index_page.html template, I have the following code
{% standard_index_listing calling_page=self %}
and it displays all the child pages with links, but I would also like to display a list of all the child links on the child pages as well.
I hope this is making sense and someone can lend a hand. Thank you.
In essence you traverse the tree structure of your page hierarchy that is provided to Wagtail by Django-Treebeard.
Many front-end frameworks do not allow for multiple levels of menus as some consider it outside of best practices. However, with a library such as SmartMenus you can display this structure with a little elbow grease.
For my needs, there was no easy solution to this. So, while I want to share an example of how I went about this, it may be missing explanation. If you have any questions, I'd be happy to answer them.
I struggled with this for awhile and while there may be easier methods to traverse the tree, I built the following method as my needs expanded. It allows us to traverse all live pages in our site, check when the current page is being rendered in the menu, and allows for fine-grained control over rendering.
Here's what we're going to do:
Create a few template tags that will get the site root of the
current site, loop through direct children of the site root, and loop through any lower levels of children, while looping through children of the current menuitem when discovered at each level.
In your base template, this means we need to:
{% load demo_tags %} to import our custom template tags
Call {% top_menu calling_page=self %} to get and render all
direct children of the site root. These are items that would be shown across a standard menu bar.
Call {% top_menu_children parent=menuitem %} within the template
rendered by {% top_menu %} to get and render all second- and
lower-level children pages. This encompasses all menu items to be shown when hovering on the parents menu item.
Here's the custom demo_tags.py file I created to traverse all levels of the page hierarchy. The beauty of this is that it does not require any custom context data to be supplied; it works out of the box with Wagtail!
#register.assignment_tag(takes_context=True)
def get_site_root(context):
'''
Returns a core.Page, not the implementation-specific model used
so object-comparison to self will return false as objects would differ
'''
return context['request'].site.root_page
def has_menu_children(page):
'''
Returns boolean of whether children pages exist to the page supplied
'''
return page.get_children().live().in_menu().exists()
#register.inclusion_tag('info_site/tags/top_menu.html', takes_context=True)
def top_menu(context, parent, calling_page=None):
'''
Retrieves the top menu items - the immediate children of the parent page
The has_menu_children method is necessary in many cases. For example, a bootstrap menu requires
a dropdown class to be applied to a parent
'''
root = get_site_root(context)
try:
is_root_page = (root.id == calling_page.id)
except:
is_root_page = False
menuitems = parent.get_children().filter(
live=True,
show_in_menus=True
).order_by('title')
for menuitem in menuitems:
menuitem.show_dropdown = has_menu_children(menuitem)
return {
'calling_page': calling_page,
'menuitems': menuitems,
'is_root_page':is_root_page,
# required by the pageurl tag that we want to use within this template
'request': context['request'],
}
#register.inclusion_tag('my_site/tags/top_menu_children.html', takes_context=True)
def top_menu_children(context, parent, sub=False, level=0):
''' Retrieves the children of the top menu items for the drop downs '''
menuitems_children = parent.get_children().order_by('title')
menuitems_children = menuitems_children.live().in_menu()
for menuitem in menuitems_children:
menuitem.show_dropdown = has_menu_children(menuitem)
levelstr= "".join('a' for i in range(level)) # for indentation
level += 1
return {
'parent': parent,
'menuitems_children': menuitems_children,
'sub': sub,
'level':level,
'levelstr':levelstr,
# required by the pageurl tag that we want to use within this template
'request': context['request'],
}
In essence, there are three levels of pages rendered:
The site root is called by {% get_site_root %}
First-level children are called by {% top_menu %}
Second- and lower-level children are called by {% top_menu_children %}, which is called any time a page shown in the menu has children while rendering this tag.
In order to do this, we need to create the templates to be rendered by our top_menu and top_menu_children template tags.
Please note - these all are built for Bootstrap 3's navbar class and customized for my needs. Just customize these for your needs. The whole menu building process is called by {% top_menu_children %}, so place this tag in your base template where you want the menus rendered. Change top_menu.html to reflect the overall structure of the menu and how to render each menuitem. Change children_items.html to reflect how you want children of all top-menu items, at any depth, rendered.
my_site/tags/top_menu.html
{% load demo_tags wagtailcore_tags static %}
{% get_site_root as site_root %}
{# FOR TOP-LEVEL CHILDREN OF SITE ROOT; In a nav or sidebar, these are the menu items we'd generally show before hovering. #}
<div class="container">
<div class="collapse navbar-collapse" id="navbar-collapse-3">
<ul class="nav navbar-nav navbar-left">
{% for menuitem in menuitems %}
<li class="{% if menuitem.active %}active{% endif %}">
{% if menuitem.show_dropdown %}
<a href="{{ menuitem.url }}">{{ menuitem.title }}
<span class="hidden-lg hidden-md hidden-sm visible-xs-inline">
<span class="glyphicon glyphicon-chevron-right"></span>
</span>
</a>
{% top_menu_children parent=menuitem %}
{% else %}
{{ menuitem.title }}
{% endif %}
</li>
{% endfor %}
</ul>
</div>
</div>
my_site/tags/children_items.html
{% load demo_tags wagtailcore_tags %}
{# For second- and lower-level decendents of site root; These are items not shown prior to hovering on their parent menuitem, hence the separate templates (and template tags) #}
<ul class="dropdown-menu">
{% for child in menuitems_children %}
{% if child.show_dropdown %}
<li>
<a href="{% pageurl child %}">
{% for i in levelstr %}&nbsp&nbsp{% endfor %}
{{ child.title }}
<span class="glyphicon glyphicon-chevron-right"></span>
</a>
{# On the next line, we're calling the same template tag we're rendering. We only do this when there are child pages of the menu item being rendered. #}
{% top_menu_children parent=child sub=True level=level %}
{# ^^^^ SmartMenus is made to render menus with as many levels as we like. Bootstrap considers this outside of best practices and, with version 3, has deprecated the ability to do so. Best practices are made to be broken, right :] #}
</li>
{% else %}
<li>
<a href="{% pageurl child %}">
<!-- Allows for indentation based on depth of page in the site structure -->
{% for i in levelstr %}&nbsp&nbsp{% endfor %}
{{ child.title }}
</a>
</li>
{% endif %}
{% endfor %}
</ul>
Now, in your base level template (let's assume you are using one; if not, get to it :) ) you can traverse the menu while keeping clutter cleared away to the templates used by your inclusion_tags.
my_site/base.html
<ul class="nav navbar-nav navbar-left">
{% for menuitem in menuitems %}
<li class="{% if menuitem.active %}active{% endif %}">
{% if menuitem.show_dropdown %}
<a href="{{ menuitem.url }}">{{ menuitem.title }}
<span class="hidden-lg hidden-md hidden-sm visible-xs-inline">
<span class="glyphicon glyphicon-chevron-right"></span>
</span>
</a>
{% top_menu_children parent=menuitem %}
{% else %}
{{ menuitem.title }}
{% endif %}
</li>
{% endfor %}
</ul>
I wrote a blog post about this - check it out for more details. Or, head over to Thermaline.com to see it in action, though I think there's not multiple levels of depth right now. IF THERE WERE, they'd be rendered automatically :)
Now, this example is for a navbar, but it could easily be adapted for a sidebar.
All you need to do is:
Include demo_tags in your base template
Call {% top_menu %} where you wish to render your menus.
Customize top_menu.html and children_items.html to render the
first and then all subsequent levels of pages.
Shout out to Tivix for their post on two-level menus that was a great starting point for me!