Using Taggit with custom view Django - python

I am currently trying to implement a tag system into my Django project. I am trying to add the tags within each post, and have a category on the right hand side that displays maybe 10-20 of the tags. I am trying to implement this into the feed view, but i am unsure of how to call the slug for each tag in order to do /posts/tag/feed. So once you click on a tag it will redirect to the slug of the tag. Which would make the tag clickable. I tried to follow the link below but it only shows how to do it with the class view.
https://godjango.com/33-tagging-with-django-taggit/
views.py
def post_feed(request):
if not request.user.is_staff or not request.user.is_superuser:
raise Http404
queryset_list = Post.objects.all()
tags = Tag.objects.all()
query = request.GET.get("q")
if query:
queryset_list = queryset_list.filter(
Q(title__icontains=query)|
Q(tags__icontains=query)|
Q(description__icontains=query)|
Q(user__first_name__icontains=query)|
Q(user__last_name__icontains=query)
).distinct()
paginator = Paginator(queryset_list, 5)
page_request_var = "page"
page = request.GET.get(page_request_var)
try:
queryset = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
queryset = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
queryset = paginator.page(paginator.num_pages)
context = {
"object_list": queryset,
"title": "List",
"page_request_var": page_request_var,
}
return render(request, "post_feed.html", context)
And here is my url
url(r'^tag/(?P<slug>[-\w]+)/$', post_feed, name='tagged'),
the Tag.objects.all() only pulls up the tags but doesnt request the slugs.
I am unsure of how to add this to my view without changing it.
from django.views.generic import DetailView, ListView
from taggit.models import Tag
from .models import Product
taggit view to add url and query slug:
class TagMixin(object):
def get_context_data(self, kwargs):
context = super(TagMixin, self).get_context_data(kwargs)
context['tags'] = Tag.objects.all()
return context
class ProductDetail(DetailView):
template_name = 'product/detail.html'
context_object_name = 'product'
model = Product
class ProductIndex(TagMixin, ListView):
template_name = 'product/index.html'
model = Product
paginate_by = '10'
queryset = Product.objects.all()
context_object_name = 'products'
class TagIndexView(TagMixin, ListView):
template_name = 'product/index.html'
model = Product
paginate_by = '10'
context_object_name = 'products'
def get_queryset(self):
return Product.objects.filter(tags__slug=self.kwargs.get('slug'))
I have been stuck on this a few days. Any advice would be helpful.
Here is my models.py, sorry had to format it this way to show up as the whole models code.
from django.db import models
from django.db.models import Count, QuerySet, F
from django.utils import timezone
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.core.urlresolvers import reverse
from django.db.models.signals import pre_save
from django.utils.text import slugify
from markdown_deux import markdown
from django.utils.safestring import mark_safe
from taggit.managers import TaggableManager
from comments.models import Comment
def upload_location(instance, filename):
return "%s/%s" %(instance.slug, filename)
class Post(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, default=1 )
title = models.CharField(max_length=75)
slug = models.SlugField(unique=True)
image = models.ImageField(
upload_to=upload_location,
null=True,
blank=True,
width_field="width_field",
height_field="height_field")
height_field = models.IntegerField(default=0)
width_field = models.IntegerField(default=0)
description = models.TextField()
tags = TaggableManager()
public = models.BooleanField(default=False)
updated = models.DateTimeField(auto_now_add=False, auto_now=True)
created = models.DateTimeField(auto_now_add=True, auto_now=False)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse("posts:detail", kwargs={"slug": self.slug})
class Meta:
ordering = ["-created", "-updated" ]
def get_markdown(self):
description = self.description
markdown_text = markdown(description)
return mark_safe(markdown_text)
#property
def comments(self):
instance = self
qs = Comment.objects.filter_by_instance(instance)
return qs
#property
def get_content_type(self):
instance = self
content_type = ContentType.objects.get_for_model(instance.__class__)
return content_type
def create_slug(instance, new_slug=None):
slug = slugify(instance.title)
if new_slug is not None:
slug = new_slug
qs = Post.objects.filter(slug=slug).order_by("-id")
exists = qs.exists()
if exists:
new_slug = "%s-%s" %(slug, qs.first().id)
return create_slug(instance, new_slug=new_slug)
return slug
def pre_save_post_receiver(sender, instance, *args, **kwargs):
if not instance.slug:
instance.slug = create_slug(instance)
pre_save.connect(pre_save_post_receiver, sender=Post)
here is my template
<div class="row">
<div class="col-sm-2">
<div class="panel panel-primary">
<div class="panel-heading">
Tags
</div>
<div class="panel-body">
<ul class="list-group">
{% for tag in tags %}
<li></li>
{% empty %}
<li>No Tags</li>
{% endfor %}
</ul>
</div>
</div>
</div>
</div>
<div class="container">
<div class='col-sm-6 col-sm-offset-3'>
<h1> Post Feed </h1>
<form method='GET' action'' class='row'>
<div class='col-sm-6'>
<div class='input-group'>
<input class='form-control' type='text' name='q' placeholder='Search posts' value='{{ request.GET.q }}'/>
<span class='input-group-btn'>
<input class= 'btn btn-default' type='submit' value='Search'/>
</div>
</div>
</form>
{% for obj in object_list %}
<div class="row">
<div class="col-sm-12">
<div class="thumbnail">
{% if obj.image %}
<img src='{{ obj.image.url }}' class='img-responsive' />
{% endif %}
<div class="caption post-detail-item">
<h3><a href='{{ obj.get_absolute_url }}'><strong>{{ obj.title }}</strong></a> <small>{{ obj.created|timesince }} ago</small>
</h3>
{% if obj.user.get_full_name %}<p>Poster: {{ obj.user.get_full_name }}</p>{% endif %}
{{ obj.get_markdown|truncatechars_html:50 }}
<p>Tags: {{ obj.tags|join:" | "|title }}</p>
<p>View</p>
</div>
</div>
</div>
</div>
{% endfor %}
<div class="pagination">
<span class="step-links">
{% if object_list.has_previous %}
<a href="?{{ page_request_var }}={{ object_list.previous_page_number }}{% if request.GET.q %}&
q={{ request.GET.q }}{% endif %}">previous</a>
{% endif %}
<span class="current">
Page {{ object_list.number }} of {{ object_list.paginator.num_pages }}.
</span>
{% if object_list.has_next %}
next
{% endif %}
</span>
</div>
<footer>
<p class="pull-right">Back to top</p>
<p>© 2016 Holms, Inc. · Terms</p>
</footer>
</div>
{% endblock content %}
</div>

Just copy and paste these.
Change the urls.py entry to this:
url(r'^tag/(?P<pk>[-\w]+)/$', tag_list, name='tagged'),
Your post_feed function to this (views.py):
def post_feed(request):
if not request.user.is_staff or not request.user.is_superuser:
raise Http404
queryset = Post.objects.all()
query = request.GET.get("q")
if query: # this is a separate variable (for searching I'm assuming)
queryset_list = queryset_list.filter(
Q(title__icontains=query)|
Q(tags__icontains=query)| # I would consider taking this out. It's gonna cause problems.
Q(description__icontains=query)|
Q(user__first_name__icontains=query)|
Q(user__last_name__icontains=query)
).distinct()
# bring pagination/tag lookup outside of the if query block -- so you don't NEED a query
paginator = Paginator(queryset, 5)
page_request_var = "page"
page = request.GET.get(page_request_var)
try:
queryset = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
queryset = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
queryset = paginator.page(paginator.num_pages)
context = {
"object_list": queryset,
"tags": tags.objects.all()[0:20], # first 20 tags of all tags
"title": "List",
"page_request_var": page_request_var,
}
return render(request, "post_feed.html", context)
And your new function to show just posts based on a specific tag to this (views.py):
""" modelled after the function above -- so it's easy to understand """
def tag_list(request, tag_id):
if not request.user.is_staff or not request.user.is_superuser:
raise Http404
queryset = Post.objects.filter(tag__id=tag_id)
paginator = Paginator(queryset, 5)
page_request_var = "page"
page = request.GET.get(page_request_var)
try:
queryset = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
queryset = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
queryset = paginator.page(paginator.num_pages)
context = {
"object_list": queryset,
"tags": tags.objects.all()[0:20], # first 20 tags of all tags
"title": "List",
"page_request_var": page_request_var,
}
return render(request, "post_feed.html", context)
then change the template to (post_feed.html):
<li>{{tag.name}}</li>
also, read this:
https://docs.djangoproject.com/en/1.9/topics/http/urls/

Related

Django create parent model and child model in one form

I am learning Python and Django, and I am trying to create a recipe with a form. The problem is I have two models, Recipe and RecipeIngredient and I don't know how to add recipe ingredients to the form since it requires the primary key of the recipe and from what I understand, the key is not created until after the form is saved? So how can I create a recipe with both the Recipe and RecipeIngredient when Recipe is not yet initialized?
Models.py:
class Recipe(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
image = models.ImageField(upload_to='image/', blank=True, null=True)
name = models.CharField(max_length=220) # Lasanga
description = models.TextField(blank=True, null=True)
notes = models.TextField(blank=True, null=True)
cookTime = models.CharField(max_length=50, blank=True, null=True)
timeStamp = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
active = models.BooleanField(default=True)
#property
def title(self):
return self.name
def get_absolute_url(self):
return reverse("recipes:detail", kwargs={"id": self.id}) # recipes is from url.py app_name
def get_hx_url(self):
return reverse("recipes:hx-detail", kwargs={"id": self.id}) # recipes is from url.py app_name
def get_edit_url(self):
return reverse("recipes:update", kwargs={"id": self.id})
def get_image_upload_url(self):
return reverse("recipes:recipe-ingredient-image-upload", kwargs={"parent_id": self.id})
def get_delete_url(self):
return reverse("recipes:delete", kwargs={"id": self.id})
def get_ingredients_children(self):
return self.recipeingredient_set.all()
def get_instruction_children(self):
return self.recipeinstruction_set.all()
class RecipeIngredient(models.Model):
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
name = models.CharField(max_length=220) # grilled chicken pasta
description = models.TextField(blank=True, null=True)
quantity = models.CharField(max_length=50, blank=True, null=True)
unit = models.CharField(max_length=50, validators=[validate_unit_of_measure], blank=True, null=True)
instructions = models.TextField(blank=True, null=True)
timeStamp = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
active = models.BooleanField(default=True)
def get_absolute_url(self):
return self.recipe.get_absolute_url() # recipe cannot be none
def get_hx_edit_url(self):
kwargs = {
"parent_id": self.recipe.id,
"id": self.id
}
return reverse("recipes:hx-ingredient-detail", kwargs=kwargs)
def get_delete_url(self):
kwargs = {
"parent_id": self.recipe.id,
"id": self.id
}
return reverse("recipes:ingredient-delete", kwargs=kwargs)
Views.py
#login_required
def recipe_create_view(request):
form = RecipeForm(request.POST or None)
context = {
"form": form,
}
if form.is_valid():
obj = form.save(commit=False)
obj.user = request.user
obj.save()
if request.htmx: # necessary to pass headers from htmx response if we want the django to recognise the htmx change
headers = {
"HX-Redirect": obj.get_absolute_url()
}
return HttpResponse("Created", headers=headers)
# if request.htmx: # could use this but the url doesn't update, stays as create, would need to use HX-Push header & HttpResponse + context somehow
# context = {
# "object": obj
# }
# return render(request, "recipes/partials/detail.html", context)
return redirect(obj.get_absolute_url())
return render(request, "recipes/create.html", context)
#login_required
def recipe_ingredient_update_hx_view(request, parent_id=None, id=None): # this is both create & edit, can create
if not request.htmx:
raise Http404
try:
parent_obj = Recipe.objects.get(id=parent_id, user=request.user)
except:
parent_obj = None
if parent_obj is None:
return HttpResponse("Not Found.")
instance = None
if id is not None:
try:
instance = RecipeIngredient.objects.get(recipe=parent_obj, id=id) # think of this as an object if that helps
except:
instance = None
form = RecipeIngredientForm(request.POST or None, instance=instance)
url = reverse("recipes:hx-ingredient-create", kwargs={"parent_id": parent_obj.id})
if instance:
url = instance.get_hx_edit_url()
context = {
"url": url,
"form": form,
"object": instance
}
if form.is_valid():
new_obj = form.save(commit=False)
if instance is None:
new_obj.recipe = parent_obj
new_obj.save()
context['object'] = new_obj # because it's possible the object/instance in None
return render(request, "recipes/partials/ingredient-inline.html", context)
return render(request, "recipes/partials/ingredient-form.html", context)
forms.py
from django import forms
from .models import Recipe, RecipeIngredient
class RecipeForm(forms.ModelForm):
class Meta:
model = Recipe
fields = ['name', 'image', 'description', 'notes', 'cookTime']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields:
new_data = {
"placeholder": f"Recipe {str(field)}",
"class": "form-control",
}
self.fields[str(field)].widget.attrs.update(new_data)
class RecipeIngredientForm(forms.ModelForm):
class Meta:
model = RecipeIngredient
fields = ['name', 'quantity', 'unit']
urls.py
from django.urls import path
from .views import (
recipe_list_view,
recipe_delete_view,
recipe_create_view,
recipe_update_view,
recipe_detail_hx_view,
recipe_ingredient_update_hx_view,
recipe_ingredient_delete_view,
recipe_ingredient_image_upload_view,
recipe_ingredient_url_scrape_view
)
app_name='recipes' # allows use of recipes:list as a reverse url call
urlpatterns = [
path('', recipe_list_view, name='list'), # index / home / root
path('create/>', recipe_create_view, name='create'),
path('hx/<int:parent_id>/ingredient/<int:id>', recipe_ingredient_update_hx_view, name='hx-ingredient-detail'), #or detail
path('hx/<int:parent_id>/ingredient/', recipe_ingredient_update_hx_view, name='hx-ingredient-create'),
]
create.html
{% extends "base.html" %}
{% load crispy_forms_tags %}
{% load static %}
{% block content %}
<div class="container-fluid px-5">
<h1 class="pb-5">Create Recipe</h1>
<div id="recipe-container">
<form action='.' method="POST" hx-post='.'>
{% csrf_token %}
<div class='row'>
<div class="row d-flex pb-5">
<div class="col-12 col-lg-6 justify-content-center d-flex order-first order-lg-last pictureBox"
style="height: 400px; width:450; border: solid tomato 1px;">
<div class="align-self-center">
{{ form.image|as_crispy_field }}
</div>
</div>
<div class="col-12 col-lg-6 order-lg-first">
<div class="pb-3">{{ form.name|as_crispy_field }}</div>
<div class="pb-3">{{ form.description|as_crispy_field }}</div>
</div>
</div>
<div class="col-12 col-md-6">
{{ form.notes|as_crispy_field }}
</div>
<div class="col-12 col-md-6">
{{ form.cookTime |as_crispy_field }}
</div>
</div>
</div>
<div class='col-12 col-md-4'>
<!-- ADD INGREDIENTS ? -->
</div>
<div class="htmx-indicator">Loading...</div>
<div class="d-flex">
<button class="btn btn-success htmx-inverted-indicator" style='margin-top:10px;' type='submit'>Save</button>
<a class="btn btn-danger" href='{% url "recipes:list" %}'>Delete</a>
</div>
{% if message %}
<p>{{ message }}</p>
{% endif %}
</form>
</div>
</div>
{% endblock content %}
ingredient-form.html
<form action='.' method="POST" hx-post='{% if url %} {{ url }} {% else %} . {{% endif %}' hx-swap='outerHTML'>
{% csrf_token %}
{{ form.as_p}}
<div class="htmx-indicator">Loading...</div>
<button class="htmx-inverted-indicator" style='margin-top:10px;' type='submit' >Save</button>
</form>
ingredient-inline.html
<div class="py-1" id="ingredient-{{object.id}}">
<p>{% if not edit %} <b>{{ object.quantity }} {% if object.unit %} {{ object.unit }} {% endif %}</b> {% else %} {{ object.quantity }} {{ object.unit }} {% endif %} - {{ object.name }}</p>
{% if edit %}
<button class="btn btn-primary" hx-trigger='click' hx-get='{{ object.get_hx_edit_url }}' hx-target="#ingredient-{{object.id}}">Edit</button> <!-- target will replace whole div-->
<button class="btn btn-danger" href='{{ object.get_delete_url }}' hx-post='{{ object.get_delete_url }}' hx-confirm="Are you sure you want to delete {{ object.name }}?" hx-trigger='click' hx-target="#ingredient-{{object.id}}" hx-swap="outerHTML">Delete</button>
{% endif %}
</div>
The key to this problem is using a formset, as you will likely want to save multiple ingredients to the recipe. Django documentation outlines how to use them. Your view would end up looking something like below, allowing you to save the parent model, which will give you the parent instance/primary key to then save the ingredients.
def recipe_create_view(request):
form = RecipeForm(request.POST or None)
RecipeIngredientFormset = formset_factory(RecipeIngredientForm)
formset = RecipeIngredientFormset(request.POST or None)
context = {
"form": form,
"formset": formset,
}
if request.method == "POST":
if form.is_valid() and formset.is_valid():
parent = form.save(commit=False)
parent.user = request.user
parent.save()
#recipe ingredients
for form in formset:
child = form.save(commit=False)
child.recipe = parent
child.save()

How to fix error 'str' object has no attribute 'relative_url'

I am trying to pass some wagtail contexts to the search page of a django project. The post title appears but the image, description and other parameters do not appear in the search template. How do I pass these contexts to the search template?
This is the error I get
AttributeError at /search/
'str' object has no attribute 'relative_url'
Request Method: GET
Request URL: http://127.0.0.1:8000/search/?q=hel
Django Version: 3.1.3
Exception Type: AttributeError
Exception Value:
'str' object has no attribute 'relative_url'
Exception Location: C:\Users\Bree\AppData\Roaming\Python\Python37\site-packages\wagtail\contrib\routable_page\templatetags\wagtailroutablepage_tags.py, line 25, in routablepageurl
Python Executable: C:\Program Files\Python37\python.exe
Python Version: 3.7.3
This is the search.view.py code
from django.shortcuts import render
from wagtail.core.models import Page
from wagtail.search.models import Query
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
from news.models import NewsPage, NewsCategory
def search(request):
posts = NewsPage.objects.live().public()
categories = NewsCategory.objects.all()
# Search
search_query = request.GET.get('q', None)
if search_query:
search_results = NewsPage.objects.live().search(search_query)
# Log the query so Wagtail can suggest promoted results
Query.get(search_query).add_hit()
reversenews = list(reversed(search_results))
# Pagination
paginator = Paginator(reversenews, 16) #show 10 articles per page
page = request.GET.get('page')
try:
search_results = paginator.page(page)
except PageNotAnInteger:
search_results = paginator.page(1)
except EmptyPage:
search_results = paginator.page(paginator.num_pages)
else:
search_results = NewsPage.objects.none()
# Render template
return render(request, 'search/search.html', {
'search_query': search_query,
'search_results': search_results,
'recent_posts' : posts,
'categories' : categories
})
this is the search template search.html
{% extends "news/news_index_page.html" %}
{% load static wagtailcore_tags %}
{% load wagtailembeds_tags %}
{% load wagtailcore_tags %}
{% load static wagtailuserbar %}
{% load wagtailimages_tags %}
{%load static%}
{% load wagtailcore_tags %}
{% load wagtailroutablepage_tags %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-md-8">
<h3>Query Results FOR <b>"{{ search_query }}"</b></h3>
<div class="post">
{% if search_results %}
<div class="row">
{% for result in search_results %}
<div class="col-md-6 col-sm-6">
<div class="images">
{% image result.blog_image max-770x480 as blog_img %}
<a class="images" href="{% pageurl result %}" title="images"><img src="{{result.blog_image}}" alt="images"></a>
</div>
<div class="text">
<h2>{{result.title}}</h2>
<div class="categories">
{% for cat in result.categories.all%}
{{cat.name}}
{%endfor%}
<p class="date"><i class="fa fa-clock-o"></i>{{result.date}}</p>
</div>
<p>{{result.description}}</p>
read more
</div>
{%endfor%}
</div>
{% elif search_query %}
<p> No results found</p>
<img src="{% static '/images/not_found %}" alt="image">
{% endif %}
</div>
<!-- Pagination -->
{% if search_results.paginator.num_pages > 1 %}
<div class="box center float-left space-30">
<nav class="pagination">
{% if search_results.has_previous %}
<a class="control prev" href="?page={{ search_results.previous_page_number }}" title="pre"><i class="fa fa-long-arrow-left"></i>Previous</a>
{% endif %}
<ul>
{% for page_num in search_results.paginator.page_range %}
<li class="{% if page_num == search_results.number %} active{% endif %}">{{ page_num }}</li>
{% endfor %}
</ul>
{% if search_results.has_next %}
<a class="control next" href="?page={{ search_results.next_page_number }}" title="next">Next<i class="fa fa-long-arrow-right"></i></a>
{% endif %}
</nav>
<!-- End pagination -->
</div>
{% endif %}
</div>
</div>
This is the models.py
from django.db import models
from django.shortcuts import render
from wagtail.admin.edit_handlers import FieldPanel, StreamFieldPanel, MultiFieldPanel, InlinePanel
from wagtail.snippets.edit_handlers import SnippetChooserPanel
from wagtail.core.fields import StreamField
from wagtail.core.models import Page, Orderable
from wagtail.images.edit_handlers import ImageChooserPanel
from wagtail.snippets.models import register_snippet
from modelcluster.fields import ParentalKey, ParentalManyToManyField
from wagtail.contrib.routable_page.models import RoutablePageMixin, route
from modelcluster.contrib.taggit import ClusterTaggableManager
from taggit.models import TaggedItemBase
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
from wagtailmenus.models import MenuPage
from django import forms
import datetime
from datetime import date
from streams import blocks
# Create your models here.
class NewsPageTag(TaggedItemBase):
content_object = ParentalKey(
'NewsPage',
related_name='tagged_items',
on_delete=models.CASCADE
)
class NewsIndexPage(RoutablePageMixin, Page):
custom_title = models.CharField(
max_length=100,
blank=True,
null=True,
help_text='Overwrites the default title',
)
content_panels = Page.content_panels + [
FieldPanel("custom_title"),
]
def get_context(self, request, *args, **kwargs):
"""Adding custom stuff to our context."""
context = super().get_context(request, *args, **kwargs)
posts = NewsPage.objects.live().public()
context["recent_posts"] = posts
context["tech_world"] = NewsPage.objects.live().filter(categories = 2)
context["digital_creatives"] = NewsPage.objects.live().filter(categories = 10)
context["exclusives"] = NewsPage.objects.live().filter(categories = 4)
context["news"] = NewsPage.objects.live().filter(categories = 1)
context["startups"] = NewsPage.objects.live().filter(categories = 3)
context['news_page'] = self
context['parent'] = self.get_parent().specific
context['categories'] = NewsCategory.objects.all()
return context
#route(r"^category/(?P<cat_slug>[-\w]*)/$", name="category_view")
def category_view(self, request, cat_slug):
context = self.get_context(request)
try:
category = NewsCategory.objects.all().get(slug=cat_slug)
except Exception:
category= None
if category is None:
pass
catPosts = NewsPage.objects.all().filter(categories__in=[category])
reversenews = list(reversed(catPosts))
paginator = Paginator(reversenews, 6)
page = request.GET.get("page")
try:
# If the page exists and the ?page=x is an int
posts = paginator.page(page)
except PageNotAnInteger:
# If the ?page=x is not an int; show the first page
posts = paginator.page(1)
except EmptyPage:
posts = paginator.page(paginator.num_pages)
context["posts"]= posts
context["category"] = NewsCategory.objects.all().get(slug=cat_slug)
return render(request, "news/cat_posts.html", context)
class NewsPage(Page):
"""Blog detail page."""
blog_image = models.ForeignKey(
"wagtailimages.Image",
blank=False,
null=True,
related_name="+",
on_delete=models.SET_NULL,
)
image_description = models.TextField(max_length=500, blank=True, null=True , default=" ")
categories = ParentalManyToManyField("news.NewsCategory", blank = True)
date = models.DateTimeField(verbose_name="Post date", default=datetime.datetime.today)
tags = ClusterTaggableManager(through=NewsPageTag, blank=True)
content = StreamField(
[
("full_richtext", blocks.RichtextBlock())
],
null=True,
blank=False
)
description = StreamField(
[
("full_richtext", blocks.RichtextBlock())
],
null=True,
blank=False
)
content_panels = Page.content_panels + [
MultiFieldPanel([
InlinePanel("news_authors", label="Author", min_num=1, max_num=4)
], heading="Author(s)"),
MultiFieldPanel([
FieldPanel("categories", widget = forms.CheckboxSelectMultiple),
FieldPanel('tags'),
FieldPanel('date'),
]),
ImageChooserPanel("blog_image"),
FieldPanel('image_description'),
StreamFieldPanel("content"),
StreamFieldPanel("description"),
]
#property
def news_page(self):
return self.get_parent().specific
def get_context(self, request, *args, **kwargs):
context = super(NewsPage, self).get_context(request, *args, **kwargs)
posts = NewsPage.objects.live().public()
context['categories'] = NewsCategory.objects.all()
context['news_page'] = self.news_page
context['post'] = self
context["recent_posts"]= NewsPage.objects.live().public()
return context
class NewsAuthor(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(max_length=500, blank=False, null=True)
email = models.URLField(blank=True, null=True)
image = models.ForeignKey(
"wagtailimages.Image",
on_delete=models.SET_NULL,
null = True,
blank = False,
related_name="+"
)
panels = [
MultiFieldPanel([
FieldPanel("name"),
FieldPanel("description"),
ImageChooserPanel("image")
], heading="Name & Image"),
MultiFieldPanel([
FieldPanel("email")
], heading="Links")
]
def __str__(self):
return self.name
register_snippet(NewsAuthor)
class NewsCategory(models.Model):
name= models.CharField(max_length=255)
slug = models.SlugField(
verbose_name="slug",
allow_unicode= True,
unique=True,
max_length=255,
help_text="A slug to identify posts by this category"
)
panels=[
FieldPanel("name"),
FieldPanel("slug")
]
class Meta:
verbose_name = 'News Category'
verbose_name_plural = 'News Categories'
ordering = ['name']
def __str__(self):
return self.name
register_snippet(NewsCategory)
This is the urls.py
from django.conf import settings
from django.conf.urls import include, url
from django.contrib import admin
from wagtail.admin import urls as wagtailadmin_urls
from wagtail.core import urls as wagtail_urls
from wagtail.documents import urls as wagtaildocs_urls
from search import views as search_views
urlpatterns = [
url(r'^django-admin/', admin.site.urls),
url(r'^admin/', include(wagtailadmin_urls)),
url(r'^documents/', include(wagtaildocs_urls)),
url(r'^search/$', search_views.search, name='search'),
]
What am I doing wrong?
It seems like the news_page variable in your template is a string, rather than a page object, as that's the only use of the {% routablepageurl %} tag in the code examples you have shared. However, you don't show where news_page is defined, so I may be mistaken.
I have now looked at your models file, and where you define news_page. You have defined news_page in the context of NewsIndexPage. This will be used when displaying a NewsIndexPage, using Wagtail's internal views, but this is not used when rendering the search view.
Here's the response context you are setting in your search/views.py:
# Render template
return render(request, 'search/search.html', {
'search_query': search_query,
'search_results': search_results,
'recent_posts' : posts,
'categories' : categories
})
Can you add news_page to the context there?

Django Filtering Articles by Categories

I'm building a news web site as a part of a task I was given, homework.
I have a "articles.html" template which renders all of my news articles by publish date.
I added a for loop in the template to loop over the Category model and display Categories as a list.
What I'm trying to do now is to filter my articles by category, so when I click "sports" on the list, my site now displays only sports related articles.
I have read so much online, and I just got confused, I'm supposed to do this today but I'm having a rough day and would appreciate some guidance !
Here are my models.py :
from django.db import models
from datetime import datetime
from autoslug import AutoSlugField
class Category(models.Model):
category_title = models.CharField(max_length=200, default="")
def __str__(self):
return self.category_title
class Article(models.Model):
title = models.CharField('title', max_length=200, blank=True)
slug = AutoSlugField(populate_from='title', default="",
always_update=True, unique=True)
author = models.CharField('Author', max_length=200, default="")
description = models.TextField('Description', default="")
is_published = models.BooleanField(default=False)
article_text = models.TextField('Article text', default="")
pub_date = models.DateTimeField(default=datetime.now, blank=True)
article_image = models.ImageField('Article Image')
article_category = models.ForeignKey(Category, on_delete="models.CASCADE", default="")
img2 = models.ImageField('Article Image 2', default="", blank=True)
img3 = models.ImageField('Article Image 3', default="", blank=True)
img4 = models.ImageField('Article Image 4', default="", blank=True)
img5 = models.ImageField('Article Image 5', default="", blank=True)
img6 = models.ImageField('Article Image 6', default="", blank=True)
def __str__(self):
return self.title
My views.py :
from django.shortcuts import render, reverse, get_object_or_404
from django.views import generic
from news.models import Article, Category
from .forms import CommentForm
from django.http import HttpResponseRedirect
class IndexView(generic.ListView):
template_name = 'news/index.html'
context_object_name = 'latest_article_list'
def get_queryset(self):
return Article.objects.order_by("-pub_date").filter(is_published=True)[:6]
class CategoryView(generic.ListView):
template_name = 'news/categories.html'
context_object_name = 'category'
def get_queryset(self):
return Category.objects.all()
def article(request, article_id):
article = get_object_or_404(Article, pk=article_id)
context = {'article': article}
return render(request, 'news/article.html', context)
class ArticlesView(generic.ListView):
context_object_name = 'latest_article_list'
template_name = 'news/articles.html'
queryset = Article.objects.order_by("-pub_date")
def get_context_data(self, **kwargs):
context = super(ArticlesView, self).get_context_data(**kwargs)
context['category'] = Category.objects.all()
return context
def add_comment_to_article(request, pk):
article = get_object_or_404(Article, pk=pk)
if request.method == "POST":
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.post = article
comment.save()
return HttpResponseRedirect(reverse('news:article', kwargs={"article_id": article.pk}))
else:
form = CommentForm()
return render(request, 'news/add_comment_to_article.html', {'form': form})
my urls.py :
from django.urls import path, include
from . import views
app_name = "news"
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
path('<int:article_id>/', views.article, name='article'),
path('articles/', views.ArticlesView.as_view(), name='articles'),
path('search/', include('haystack.urls')),
path('<int:pk>/comment/', views.add_comment_to_article, name='add_comment_to_post'),
path('category/<int:category_id>', views.CategoryView.as_view(), name="category")
]
And the template im trying to render everything in, articles.html:
<div class="container">
{% block articles %}
<!-- ***************************************** -->
<ul>
<li>Categories:</li>
{% for category in category %}
<li>
<h1>{{ category.id}}</h1>
{{ category.category_title }}
</li>
{% endfor %}
</ul>
<!-- ***************************************** -->
<hr class="hr-style1">
<h2 class="article-list-title">Article List :</h2>
<hr class="hr-style2">
<div class="container list-wrapper">
{% for article in latest_article_list %}
<div class="container">
<div class="well">
<div class="media">
<a class="pull-left" href="{% url 'news:article' article.id %}">
<img class="media-object" src="{{ article.article_image.url }}">
</a>
<div class="media-body">
<h4 class="media-heading">{{ article.title }}
</h4>
<p class="text-right">{{ article.author }}</p>
<p>{{ article.description }}</p>
<ul class="list-inline list-unstyled">
<li><span><i class="glyphicon glyphicon-calendar"></i> {{ article.pub_date }} </span></li>
<li>|</li>
<span><i class="glyphicon glyphicon-comment"></i> 2 comments</span>
<li>|</li>
<li>
<span class="glyphicon glyphicon-star"></span>
<span class="glyphicon glyphicon-star"></span>
<span class="glyphicon glyphicon-star"></span>
<span class="glyphicon glyphicon-star"></span>
<span class="glyphicon glyphicon-star-empty"></span>
</li>
<li>|</li>
<li>
<span><i class="fa fa-facebook-square"></i></span>
<span><i class="fa fa-twitter-square"></i></span>
<span><i class="fa fa-google-plus-square"></i></span>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
Apologies for the messy code, still trying to learn.
Thank you for taking the time to read this !
I'm not asking you to do this for me, an explanation will be enough ! Thanks !
You can override the get_queryset method on your ArticlesView by passing a filter param as follow:
class ArticlesView(generic.ListView):
context_object_name = 'latest_article_list'
template_name = 'news/articles.html'
def get_context_data(self, **kwargs):
context = super(ArticlesView, self).get_context_data(**kwargs)
# It would be best to rename the context to categories
# as you are returning multiple objects
context['categories'] = Category.objects.all()
return context
def get_queryset(self):
# Note you do not have to use the article PK you can use any
# article field just update the template argument accordingly
category_pk = self.request.GET.get('pk', None)
if category_pk:
return Article.objects.filter(article_category__pk=category_pk).order_by("-pub_date")
return Article.objects.order_by("-pub_date")
In your template you can then update the category links as follow:
<ul>
<li>Categories:</li>
{% for category in categories %}
<li>
<h1>{{ category.id}}</h1>
{{ category.category_title }}
</li>
{% endfor %}
<ul>
Give this a try and let me know if it works

NoReverseMatch at /posts/ Reverse for 'detail' with keyword arguments '{'id': 1}' not found. 1 pattern(s) tried: ['posts/(?P<slug>[^/]+)/$']

I am trying to use slug as url in post detail. But this gives me error like this
.It says error is realted to posts.view.detail when my database is empty. but after adding few posts from admin interface i get error something like this
What is wrong here?
NoReverseMatch at /posts/
Reverse for 'detail' with keyword arguments '{'id': 1}' not found. 1 pattern(s) tried: ['posts/(?P[^/]+)/$']
posts/models.py
from django.db import models
from django.urls import reverse
from django.db.models.signals import pre_save
from django.utils.text import slugify
# Create your models here.
def upload_location(instance, filename):
#filebase, extension = filename.split(".")
# return "%s/%s.%s" %(instance.id,instance.id, extension)
return "%s/%s" %(instance.id, filename)
class Post(models.Model):
title = models.CharField(max_length= 120)
slug = models.SlugField(unique=True)
image = models.ImageField(upload_to=upload_location,
null=True,
blank = True,
width_field="width_field",
height_field="height_field")
height_field = models.IntegerField(default=0)
width_field = models.IntegerField(default=0)
content = models.TextField()
updated = models.DateTimeField(auto_now=True, auto_now_add=False)
timestamp = models.DateTimeField(auto_now=False, auto_now_add=True)
def __unicode__(self):
return self.title
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse("posts:detail", kwargs={"slug": self.slug})
#return "/posts/%s/" %(self.id)
class Meta:
ordering: ["-timestamp", "-updated"]
def create_slug(instance, new_slug=None):
slug = slugify(instance.title)
if new_slug is not None:
slug = new_slug
qs = Post.objects.filter(slug=slug).order_by("-id")
exists = qs.exists()
if exists:
new_slug = "%s-%s" %(slug, qs.first().id)
return create_slug(instance, new_slug=new_slug)
return slug
def pre_save_post_receiver(sender, instance, *args, **kwargs):
if not instance.slug:
instance.slug = create_slug(instance)
pre_save.connect(pre_save_post_receiver, sender=Post)
posts/urls.py
from django.urls import path
from .views import (
posts_create,
posts_delete,
posts_detail,
posts_list,
posts_update
)
app_name = "posts"
urlpatterns = [
path("", posts_list, name="list"),
path("<slug>/", posts_detail, name="detail"),
path("create/", posts_create, name="create"),
path("<slug>/edit/", posts_update, name="update"),
path("<slug:slug>/delete/", posts_delete, name="delete"),
]
posts/view.py
def posts_detail(request, slug=None):
#instance = Post.objects.get(id=1)
instance = get_object_or_404(Post, slug=slug)
context = {
"title": instance.title,
"instance": instance
}
return render(request, "post_detail.html", context)
def posts_list(request):
queryset_list = Post.objects.all()
paginator = Paginator(queryset_list, 5)
page_request_var= "page"
page = request.GET.get(page_request_var)
try:
queryset = paginator.page(page)
except PageNotAnInteger:
queryset = paginator.page(1)
except EmptyPage:
queryset = paginator.page(paginator.num_pages)
#contacts = paginator.get_page(page)
context = {
"object_list": queryset,
"title": "List",
"page_request_var": page_request_var
}
return render(request, "post_list.html", context)
templates/post_detail.html
{%extends 'base.html' %}
{% block head %} {{instance.title}} {%endblock head %}
{% block content %}
<div class="col-sm-6 col-sm-offset-3">
{% if instance.image %}
<img src="{{instance.image.url}}" class="img-responsive"/>
{% endif %}
<h1>{{title}}<small>{{instance.timestamp}}</small></h1>
{{instance.content}}<br/>
</div>
{%endblock content %}
post_list.html
{% extends "base.html" %}
{%block content %}
<h1>{{title}} <small>{% if instance.draft %}<span style="color: 'red';">Draft{{instance.publish}}</span>{% endif %}</small></h1>
{% for obj in object_list %}
<div>
<!-- {% url 'posts:detail' id=obj.id %} -->
{{obj.title}}<br/>
{{obj.publish}}<br/>
{{obj.updated}}<br/>
{{obj.content|linebreaks|truncatechars:120}}<br/>
{% endfor %}
</div>
{% for contact in contacts %}
{# Each "contact" is a Contact model object. #}
{{ contact.full_name|upper }}<br>
{% endfor %}
<div class="pagination">
<span class="step-links">
{% if object_list.has_previous %}
« first
previous
{% endif %}
<span class="current">
Page {{ object_list.number }} of {{ query_list.paginator.num_pages }}.
</span>
{% if object_list.has_next %}
next
last »
{% endif %}
</span>
</div>
{%endblock content %}

How do I search a database and print results?

What I'm looking to do is make it to where I can search the database directly without having the entire database visible on search page. The search page should be empty. You do a search and it goes straight to the detail page of the db entry which matches your search criteria. If there is no match it shows "No matches found". How can I do this?
This is what I have so far. This prints/lists every entry in the database on the same page as my search box and when a search is done the list gets cut down to however many entries match the search criteria. Every db entry has a link you can click on which will take you to a detail page which pulls up that entry's info.
I'm using Django 2.0.
Manage.py
class PostManager(models.Manager):
def active(self, *args, **kwargs):
return super(PostManager, self).filter(draft=False).filter(publish__lte=timezone.now())
def upload_location(instance, filename):
return "%s/%s" %(instance.slug, filename)
class Remote(models.Model):
brand = models.CharField(max_length=20)
model = models.CharField(max_length=25)
type = models.CharField(max_length=50)
orig = models.TextField()
comp = models.TextField(blank=True)
image = models.ImageField(upload_to=upload_location,
null=True, blank=True, default=0,
timestamp = models.DateTimeField(auto_now=False, auto_now_add=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE, default=1)
slug = models.SlugField(unique=True)
objects = PostManager()
def __str__(self):
return self.model
def get_absolute_url(self):
return reverse("mainapp:detail", kwargs={"slug": self.slug})
def create_slug(instance, new_slug=None):
slug = slugify(instance.model) #slugify title
if new_slug is not None:
slug = new_slug
qs = Remote.objects.filter(slug=slug).order_by("-id")
exists = qs.exists()
if exists:
new_slug = "%s-%s" %(slug, qs.first().id)
return create_slug(instance, new_slug=new_slug)
return slug
def pre_save_post_receiver(sender, instance, *args, **kwargs):
if not instance.slug:
instance.slug = create_slug(instance)
pre_save.connect(pre_save_post_receiver, sender=Remote)
Views.py
def detail(request, slug=None):
instance = get_object_or_404(Remote, slug=slug)
share_string = quote_plus(instance.orig)
context={
"model": instance.model,
"instance": instance,
"share_string": share_string,
}
return render(request, 'mainapp/remote_detail.html', context)
def remote_list(request):
today = timezone.now().date()
queryset_posts = Remote.objects.all()
if request.user.is_staff or request_user_is_superuser:
queryset_posts = Remote.objects.all()
query = request.GET.get("q") # search stuff
if query:
queryset_posts = queryset_posts.filter(
Q(model__icontains=query)|
Q(orig__icontains=query)
).distinct() #.distinct() doesn't allow duplicates to show up
paginator = Paginator(queryset_posts,10) # Show 25 contacts per page
page_request_var = "pagina" #this changes "Page name in pagination
page = request.GET.get(page_request_var)
queryset = paginator.get_page(page)
context={
"object_list": queryset,
"title": "List",
"page_request_var": page_request_var,
"today": today,
}
return render(request, 'mainapp/remote_list.html', context)
HTML for Remote_list view
<form class='mb-2' method='GET' action=''>
<input type='text' name='q' placeholder='Search posts' value='{{ request.GET.q }}'/>
<input href='{{ obj.get_absolute_url }}' type='submit' value='Search'/>
</form>
{% for obj in object_list %}
{% if obj.image %}
<img class="card-img-top" src="{{ obj.image.url }}" alt="Image" style="max-width:100px;">
{% endif %}
<h5 class="card-title"><a href='{{ obj.get_absolute_url }}'>{{ obj.model }}</a></h5>
{% if obj.user.get_full_name %}
<p>Author: {{ obj.user.get_full_name }}</p>
{% endif %}
<p class="card-text">{{ obj.orig|truncatechars:120 }}</p>
View
{% endfor %}
HTML for Detail view
{% if instance.image %}
<img src='{{ instance.image.url }}' class='img-responsive' style='max-width:300px;' />
{% endif %}
<h3>{{ instance.brand}} {{ model }}</h3>
{% if instance.user.get_full_name %}
<p>Author: {{ instance.user.get_full_name }}</p>
{% endif %}
<h5>Originally Supplied With: </h5> {{ instance.orig }}
{% if instance.comp %}
<h5>Compatible With: </h5>{{ instance.comp }}
{% endif %}
you need to add some codes to your qs but now it's okey
you need to learn more about filtering query and passing them to your views if your models contains title field you can do {{ your_data.title }} in your template with your_data is the filtered passed query

Categories

Resources