how to handle same form on different pages (DRY) - django

I have a template called base.html. it contains a fixed search form at top of it. all of my other templates inherit from base.html. I want to my form works on every pages (right now I have 10 different pages).
One silly solution is to handle form for every single view, but it is opposite of DRY.
So how can I handle my form one time for all of views ?
NOTE: base.html is just a template and is not used directly by a view.

You need to create a separate app for 'search' and need to create views and templates accordingly.
I have an app called 'Products' in my Django project and it has the model called 'Product' and I use search to search from that model.
search/views.py:
from django.shortcuts import render
from django.views.generic import ListView
from products.models import Product
class SearchProductView(ListView):
template_name = "search/view.html"
def get_context_data(self, *args, **kwargs):
context = super(SearchProductView, self).get_context_data(*args, **kwargs)
query = self.request.GET.get('q')
context['query'] = query
# SearchQuery.objects.create(query=query)
return context
def get_queryset(self, *args, **kwargs):
request = self.request
method_dict = request.GET
query = method_dict.get('q', None) # method_dict['q']
if query is not None:
return Product.objects.search(query)
return Product.objects.featured()
'''
__icontains = field contains this
__iexact = fields is exactly this
'''
search/templates/view.html (to render the search results):
{% extends "base.html" %}
{% block content %}
<div class='row mb-3'>
{% if query %}
<div class='col-12' >
Results for <b>{{ query }}</b>
<hr/>
</div>
{% else %}
<div class='col-12 col-md-6 mx-auto py-5'>
{% include 'search/snippets/search-form.html' %}
</div>
<div class='col-12'>
<hr>
</div>
{% endif %}
</div>
<div class='row'>
{% for obj in object_list %}
<div class='col'>
{% include 'products/snippets/card.html' with instance=obj %}
{% if forloop.counter|divisibleby:3 %}
</div> </div><div class='row'><div class='col-12'><hr/></div>
{% elif forloop.counter|divisibleby:2 %}
</div> </div><div class='row'><div class='col-12'><hr/></div>
{% else %}
</div>
{% endif %}
{% endfor %}
</div>
{% endblock %}
search/templates/snippets/search-form.html:
<form method='GET' action='{% url "search:query" %}' class="form my-2 my-lg-0 search-form">
<div class='input-group'>
<input class="form-control" type="text" placeholder="Search" name='q' aria-label="Search" value='{{ request.GET.q }}'>
<span class='input-group-btn'>
<button class="btn btn-outline-success" type="submit">Search</button>
</span>
</div>
</form>
and, finally, urls.py for search app:
from django.conf.urls import url
from .views import (
SearchProductView
)
urlpatterns = [
url(r'^$', SearchProductView.as_view(), name='query'),
]
and include this in your main urls.py:
url(r'^search/', include("search.urls", namespace='search')),
I hope this helps.
Good luck!

Related

How to by default not show list of items?

There is a page where the list of elements is displayed. Also on this page there is a search by value. When I enter a value, the page only displays elements with that value.
I need to make the page show nothing but the search by default. And only after the query, the page shows the elements with the entered value. How to do it?
home.html
<div class="headtext">
<form method="GET" action="{% url 'search' %}">
<input type="search" type="text" name="q" prequired placeholder="Put appnumber">
<button type="submit">Find</button>
</form>
</div>
<div>
{% for application in object_list %}
<div>
<p>Application: {{ application.appnumber }}, status: {{ application.status }}</p>
</div>
{% endfor %}
</div>
urls.py
from django.urls import path
from .views import HomeView, Search
urlpatterns = [
path('', HomeView.as_view(), name="home"),
path('search/', Search.as_view(), name="search"),
views.py
class HomeView(ListView):
model = Application
template_name = 'home.html'
class Search(ListView):
template_name = 'home.html'
def get_queryset(self):
return Application.objects.filter(appnumber__icontains=self.request.GET.get("q"))
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context["q"] = self.request.GET.get("q")
return context
Simply check the existence of the object_list context variable:
<div class="headtext">
<form method="GET" action="{% url 'search' %}">
<input type="search" type="text" name="q" prequired placeholder="Put appnumber">
<button type="submit">Find</button>
</form>
</div>
{% if object_list %}
<div>
{% for application in object_list %}
<div>
<p>Application: {{ application.appnumber }}, status: {{ application.status }}</p>
</div>
{% endfor %}
</div>
{% endif %}
Edit: I hadn't noticed that HomeView is also a subclass of ListView. Change it to:
class HomeView(TemplateView):
template_name = 'home.html'
You can have an method that will take 'q' from request and return it or None.
Then your get_queryset can use this parameter to return object list you need.
Something like this should work
class Search(ListView):
template_name = 'home.html'
def get_q(self):
q = self.request.GET.get('q', None)
return q
def get_queryset(self):
q = self.get_q()
if not q:
return []
return Application.objects.filter(appnumber__icontains=q)

Django- Template not found

I can't seem to get my delete, edit and add review functionality working. The errors come as soon as I try to navigate to the urls I have set up. When I try and add a new review using my link on the reviews page I get the below message:
TemplateDoesNotExist at /reviews/add
I don't understand why because I have linked the url above to the template, which I have created.
The issue I have with my edit/delete views is that the url it searches for when I click the button is just /edit/ or /delete/ rather than reviews/edit/int:pk or reviews/delete/int:pk as per my urls.
I have pasted my code below, any help would be much appreciated! I have the feeling I am going to kick myself when I realise!
reviews.html:
{% extends "base.html" %}
{% load static %}
{% block content %}
<div class="container-fluid home-container">
<div class="row align-items-center">
<div class="col-sm-12 text-center mt-4">
<h2><strong>Reviews</strong></h2>
</div>
</div>
{% for review in reviews %}
<hr class="hr-1">
<div class="row featurette">
<div class="col-sm-12">
<h2 class="featurette-heading">{{ review.title }}</h2>
<p class="lead">{{ review.content }}</p>
<div class="row justify-content-between mx-1">
<p>By: {{ review.user }}</p>
<p>Created on: {{ review.created }}</p>
<p>Last Updated: {{ review.updated }}</p>
</div>
<!-- Add user authentication if -->
<div class="text-center">
<a href="edit/{{ review.id }}" class="mx-2">
<button class="positive-button mb-2">Edit</button></a>
<a href="delete/{{ review.id }}" class="mx-2 mb-2">
<button class="negative-button">Delete</button></a>
</div>
</div>
</div>
{% endfor %}
<div class="row">
<div class="col-sm-12 text-center py-4">
{% if user.is_authenticated %}
<a href="{% url 'home:add_review' %}">
<button class="positive-button-lg">Add a review</button>
</a>
{% else %}
<p>If you would like to add your own review, please login or sign up if you haven't already!</p>
{% endif %}
</div>
</div>
</div>
{% endblock %}
add_review.html:
{% extends "base.html" %}
{% load static %}
{% block content %}
<div class="container-fluid">
<div class="row justify-content-center">
<div class="col-auto text-center p-3">
<form method="post" style="margin-top: 1.3em;">
{{ review_form }}
{% csrf_token %}
<button type="submit" class="btn btn-primary btn-lg">Submit</button>
</form>
</div>
</div>
{% endblock %}
views.py:
from django.shortcuts import render
from django.views import View
from django.urls import reverse_lazy
from django.views.generic import UpdateView, DeleteView
from .models import Reviews
from .forms import ReviewForm
def home(request):
''' Returns the home page.'''
return render(request, 'home/index.html')
def reviews(request):
''' Returns the reviews page.'''
serialized_reviews = []
reviews = Reviews.objects.all()
for review in reviews:
serialized_reviews.append({
"title": review.title,
"content": review.content,
"user": review.user,
"created": review.created,
"updated": review.updated,
})
context = {
"reviews": serialized_reviews
}
print(serialized_reviews)
return render(request, 'home/reviews.html', context)
class AddReview(View):
'''View which allows the user to add a new review.'''
def get(self, request, *args, **kwargs):
review = Reviews
review_form = ReviewForm
context = {
'review': review,
'review_form': review_form,
'user': review.user,
'title': review.title,
'content': review.content,
}
return render(request, 'add_review.html', context)
def post(self, request, *args, **kwargs):
review_form = ReviewForm(data=request.POST)
if review_form.is_valid():
obj = review_form.save(commit=False)
obj.user = request.user
obj.save()
return redirect("home:reviews")
class DeleteReview(DeleteView):
'''View which allows the user to delete the selected review.'''
model = Reviews
template_name = 'delete_review.html'
success_url = reverse_lazy('reviews')
class EditReview(UpdateView):
'''View which allows the user to edit the selected review.'''
model = Reviews
template_name = 'edit_review.html'
fields = ['title', 'content']
urls.py:
from django.urls import path
from . import views
app_name = 'home'
urlpatterns = [
path('', views.home, name='home'),
path('reviews', views.reviews, name='reviews'),
path('reviews/add', views.AddReview.as_view(), name='add_review'),
path('reviews/delete/<int:pk>', views.DeleteReview.as_view(), name='delete_review'),
path('reviews/edit/<int:pk>', views.EditReview.as_view(), name='edit_review'),
]
The main difference is my app name, which is 'core'. Also, I forgot to add the content field to the model, but that is easily done, the form will just handle it as soon as you do the migration. (except on list.html)
models.py
from django.db import models
from django.contrib.auth import get_user_model
from django.utils import timezone
class Reviews(models.Model):
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
title = models.CharField(max_length=128)
created_at = models.DateTimeField(auto_now_add=timezone.now())
updated_at = models.DateTimeField(auto_now=timezone.now())
forms.py
from django import forms
from core.models import Reviews
class ReviewsForm(forms.ModelForm):
class Meta:
model = Reviews
fields = '__all__'
views.py
from core.models import Reviews
from core.forms import ReviewsForm
from django.shortcuts import render, redirect
def list_reviews(request):
reviews = Reviews.objects.all()
context = {
"reviews": reviews
}
return render(request, 'reviews/list.html', context)
def add_review(request):
if request.method == 'POST':
form = ReviewsForm(request.POST)
if form.is_valid():
form.save()
return redirect('/reviews/')
else:
form = ReviewsForm()
context = {
'form': form
}
return render(request, 'reviews/detail.html', context)
def edit_review(request, pk):
if request.method == 'POST':
form = ReviewsForm(request.POST)
if form.is_valid():
obj = Reviews.objects.get(id=pk)
obj.title = form.cleaned_data['title']
obj.user = form.cleaned_data['user']
obj.save()
return redirect('/reviews/')
else:
obj = Reviews.objects.get(id=pk)
form = ReviewsForm(instance=obj)
context = {
'form': form
}
return render(request, 'reviews/detail.html', context)
def delete_review(request, pk):
obj = Reviews.objects.get(id=pk)
obj.delete()
return redirect('/reviews/')
urls.py
from django.urls import path
import core.views as views
app_name = 'core'
urlpatterns = [
path('reviews/', views.list_reviews, name='list-reviews'),
path('reviews/add', views.add_review, name='add-review'),
path('reviews/edit/<int:pk>/', views.edit_review, name='edit-review'),
path('reviews/delete/<int:pk>/', views.delete_review, name='delete-review'),
]
list.html
{% extends "base.html" %}
{% load static %}
{% block content %}
<div class="container-fluid home-container">
<div class="row align-items-center">
<div class="col-sm-12 text-center mt-4">
<h2><strong>Reviews</strong></h2>
</div>
</div>
{% for review in reviews %}
<hr class="hr-1">
<div class="row featurette">
<div class="col-sm-12">
<h2 class="featurette-heading">{{ review.title }}</h2>
<p class="lead">{{ review.content }}</p>
<div class="row justify-content-between mx-1">
<p>By: {{ review.user }}</p>
<p>Created on: {{ review.created_at }}</p>
<p>Last Updated: {{ review.updated_at }}</p>
</div>
<!-- Add user authentication if -->
<div class="text-center">
<a href="{% url 'core:edit-review' pk=review.id %}" class="mx-2">
<button class="positive-button mb-2">Edit</button></a>
<a href="{% url 'core:delete-review' pk=review.id %}" class="mx-2 mb-2">
<button class="negative-button">Delete</button></a>
</div>
</div>
</div>
{% endfor %}
<div class="row">
<div class="col-sm-12 text-center py-4">
{% if user.is_authenticated %}
<a href="{% url 'core:add-review' %}">
<button class="positive-button-lg">Add a review</button>
</a>
{% else %}
<p>If you would like to add your own review, please login or sign up if you haven't already!</p>
{% endif %}
</div>
</div>
</div>
{% endblock %}
detail.html
{% extends "base.html" %}
{% load static %}
{% block content %}
<div class="container-fluid">
<div class="row justify-content-center">
<div class="col-auto text-center p-3">
<form method="post" style="margin-top: 1.3em;">
{% csrf_token %}
<table>
{{ form }}
</table>
<button type="submit" class="btn btn-primary btn-lg">Save</button>
</form>
</div>
</div>
{% endblock %}
According to your urls, It is a review/edit/<int:pk>.
So you must add same thing in href tag.
Change this:
<a href="edit/{{ review.id }}"
To this:
<a href="review/edit/{{ review.id }}"
That's why you are getting that error
I've fixed it, firstly the path in my add_review view was wrong, which I have amended and it now works (thanks Ivan).
I also was not actually bringing the ID through on my 'reviews' view in the first place, so when following the links on my edit and review buttons, it didn't know what I meant by 'id', as I hadn't specified what that was in the view context.
Thanks for the help all!
I think you're writing in your urls the wrong way, like this below your div text-center:
<a href="edit/{{ review.id }}" class="mx-2">
It should be:
<a href="{% url 'yourappname:edit' review.id %}">

Django search pagination error, page not found error each time I try to move to page 2

I'm popping up with an error on my comic book ecommerce application. It's a Django app. Basically, the search function isn't paginating properly, giving me a page not found error each time I try to move to page 2 and onwards. It works fine in my general products listing area though, where there is no q-based search.
It works fine when I paginate just the products section, adding in...
paginate_by = 6
...into the class ListView section...
...and adding this section into the view.html section...
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
« first
previous
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
next
last »
{% endif %}
</span>
</div>
...But, once I try to paginate the search function in the same way, it appears to paginate, but page 2 and onwards just gives me a page not found error...
http://127.0.0.1:8000/search/?page=2
Request Method: GET
Request URL: http://127.0.0.1:8000/search/?page=2
Raised by: search.views.SearchProductView
....I think it has something to do with q, and the view.html section, but I'm not sure.
Any help would be much-appreciated.
Also, here's my view.html:
{% extends "base.html" %}
{% block content %}
<!-- <div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
« first
previous
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
next
last »
{% endif %}
</span>
</div> -->
<!--the heading with the result of the name-->
<div class="row mb-3">
{% if query %}
<div class="col-12">
Results for <b>{{ query }}</b>
<hr/>
</div>
{% else %}
<div class="col-12 col-md-8 mx-auto py-5">
{% include 'search/snippets/search-form.html' %}
</div>
<div class="col-12">
<hr>
</div>
{% endif %}
</div>
<!--create a card/item for each comic-->
<div class="row">
{% for obj in object_list %}
<div class="col">
{% include 'products/snippets/card.html' with instance=obj %}
{% if forloop.counter|divisibleby:3 %}
</div>
</div>
<!--or else if no item just create a blank row and column-->
<div class='row'><div class='col-12'><hr/></div>
{% else %}
</div>
{% endif %}
{% endfor %}
{% endblock %}
And here's a snippet that works within the view.html (search-form.html)
<form method="GET" action='{% url "search:query" %}' class="form my-2 my-lg-0 search-form">
<div class="input-group">
<input class="form-control" type="search" placeholder="Search" name='q' aria-label="Search" value='{{ request.GET.q }}'>
<span class='input-group-btn'>
<button class="btn btn-outline-success" type="submit">Search</button>
</span>
</div>
</form>
As well, here's my view.py backend:
from django.shortcuts import render
from django.views.generic import ListView
from products.models import Product
from django.core.paginator import Paginator
class SearchProductView(ListView):
#PROBLEM WITH PAGINATION IN THE SEarch function and view
#https://stackoverflow.com/questions/48436649/django-pagination-page-not-found?rq=1
#https://www.youtube.com/watch?v=acOktTcTVEQ
#https://www.youtube.com/watch?v=q-Pw7Le30qQ
#https://docs.djangoproject.com/en/3.1/topics/pagination/
#paginate_by = 6
template_name = "search/view.html"
def get_context_data(self, *args, **kwargs):
context = super(SearchProductView, self).get_context_data(*args, **kwargs)
context['query'] = self.request.GET.get('q')
return context
def get_queryset(self, *args, **kwargs):
request = self.request
method_dict = request.GET
query = method_dict.get('q', None) #method_dict['q']
if query is not None:
return Product.objects.search(query).order_by('title')
return Product.objects.filter(featured=True).order_by('title')
'''
__icontains = field contains this
__iexact = field is exactly this
'''
Finally, here are my urls:
from django.conf.urls import url
from .views import (
SearchProductView
)
urlpatterns = [
url(r'^$', SearchProductView.as_view(), name='query'),
]

my marketing_message is displayed only in index.html and rest of the pages middleware dont display marketing messages

I think since i am adding marketing_message in templates of views.py thats why my context is displaying message only at index.py...
i want to display my message in every page of my app.
middleware.py
from .models import MarketingMessage
from django.utils.deprecation import MiddlewareMixin
class DisplayMarketing(MiddlewareMixin):
def __init__(self, get_response):
self.get_response = get_response
def process_request(self, request):
print("something")
try:
request.session['marketing_message'] = MarketingMessage.objects.all()[0].message
except:
request.session['marketing_message'] = False
settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'marketing.middleware.DisplayMarketing',
]
added it in views.py
def index(request):
products = Product.objects.all()
marketing_message = MarketingMessage.objects.all()[0]
context = {'products':products,'marketing_message':marketing_message}
return render(request,'pro/index.html',context)
base.html
{% if marketing_message %}
<div class="alert alert-success alert-dismissible alert-top-message" role="alert">
<h3>
{{ marketing_message.message|safe }}
</h3>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
{% endif %}
index.html--
{% extends "pro/base.html" %}
{% load static %}
{% block head_title %}
Home||
{% endblock %}
{% block content %}
<div class="container">
<div class="row">
{% for product in products %}
<div class="col-sm-6 col-md-4">
<div class="thumbnail">
{% if product.productimage_set.all %}
{% for item in product.productimage_set.all %}
{% if item.featured %}
<div style="width:200px;
height:200px;
background-image:URL('{{ MEDIA_URL }}{{ item.image.url }}');
background-repeat:no-repeat;
background-size: cover;
background-position:center;
margin:0 auto;">
</div>
<!-- <img class="img-responsive" src="{{ MEDIA_URL }}{{ item.image.url }}" > -->
{% endif %}
{% endfor %}
{% else %}
<img src="{% static '/download.svg' %}" class="img-responsive" />
{% endif %}
<div class="caption">
<h3>{{ product.title }}</h3>
<p>{{ product.description|truncatewords:15 }}</p>
<p>View Button</p>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endblock %}
This means you either have to pass the marketing message on every view.
Or you could also create a custom templatetag
from django import template
register = template.Library()
#register.simple_tag
def marketing_message():
return MarketingMessage.objects.all()[0]
And then load the tag in the html and display the result.
EDIT:
To have the message on every page either you include the message in your base.html and extend the other templates from base
or you have to import and use the templatetag in every template
EDIT 2: Usage in base.html
Assuming the above code is in poll_extra.py and you have the following structure where polls your apps name is
polls/
__init__.py
models.py
templatetags/
__init__.py
poll_extras.py
views.py
the base.html needs:
{% load poll_extras %}
{% marketing_message %}
the first line loads the tag(s) and the second is the tag which returns the message, it is called by the name of the python function
this is all described in the official documentation
Edit: using Middleware
from the documentation about middleware
class SimpleMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
response = self.get_response(request)
# Code to be executed for each request/response after
# the view is called.
return response
then your middleware should adjust the context of the response after (or before, not sure if it matters in your case) the view is called
Also check out this answer maybe you try to access the value the wrong way?

Django: Joining a group Works, But I cant leave A group

I have added the join group functionality, and it works fine. Now, However, Im trying to add the leave group functionality, which seems like it would be similar, but it isn't working, and not throwing me an error either. here is the code for both join and leave group. it should be noted that there is a M2M relationship between User and Group.
(urls.py):
from . import views
from django.urls import path
app_name = 'groups'
urlpatterns = [
path('create/', views.create, name='create'),
path('index/', views.index, name='index'),
path('<int:group_id>/', views.detail, name='detail'),
path('<int:group_id>/join/', views.join, name='join'),
path('<int:group_id>/leave/', views.join, name='leave'),
]
(views.py):
def join(request, group_id):
group = get_object_or_404(Group, pk= group_id)
if request.method == 'POST':
group.members.add(request.user)
group.save()
return redirect('/groups/' + str(group_id) )
else:
return render(request, '/groups/detail.html', {'group': group})
def leave(request, group_id):
group = get_object_or_404(Group, pk= group_id)
if request.method == 'POST':
if request.user in group.members.all:
group.members.remove(request.user)
group.save()
return redirect('home')
else:
return render(request, '/groups/index.html')
groups/detail.html
{% extends "base.html" %}
{% block content %}
<div class="row">
<div class="col-4">
<h1>{{group.name}}</h1>
</div>
<div class="col-6">
<p>{{group.description}}</p>
</div>
{% if user in group.members.all %}
<div class="col-2">
<button class="btn btn-primary btn-lg btn-block"> Leave {{product.members.count}}</button>
</div>
{% else %}
<div class="col-2">
<button class="btn btn-primary btn-lg btn-block"> Join {{product.members.count}}</button>
</div>
{% endif %}
</div>
<div class="row">
<div class="col-4">
<img src="{{group.image.url}}" alt="">
</div>
</div>
<br>
<br>
<div class="row bootstrap snippets">
<div class="col-md-3 container-widget">
<div class="panel panel-info panel-widget">
<div class="panel-title text-center">
Group Members
</div>
<div class="panel-body">
{% for member in group.members.all %}
<ul class="basic-list image-list">
<li><b>{{member.username}}</li>
</ul>
{% endfor %}
</div>
</div>
</div>
</div>
<form method ='POST' id= 'leave' action="{% url 'groups:leave' group.id %}" >
{% csrf_token %}
<input type="hidden" >
</form>
<form method ='POST' id= 'join' action="{% url 'groups:join' group.id %}" >
{% csrf_token %}
<input type="hidden" >
</form>
{% endblock %}
enter code here
group.members.all is a method, you need to call it.
But don't do this; it unnecessarily queries all the members from the database. You could possibly use exists(), but actually there is no reason to check at all; remove() is a no-op if the item is not present. Just call it.
Also, you don't need to all .save() after modifying a many-to-many relationship.
You have views.join written twice, the second should be views.leave
The only problem with your leave function view is that you omitted the parenthesis to the if statement eg. if request.user in group.members.all(): so i figured that when you add the parenthesis it should work.
def leave(request, user_id):
if request.method == 'POST':
group = get_object_or_404(Group, pk=group_id)
if request.user in group.members.all():
group.members.remove(request.user)
group.save()
return redirect('home')
return HttpResponse("You have been removed successfully " + str(user.username))
else:
return render(request, '/groups/index.html')
I think it might be better to rename your group model in other to avoid naming conflict with the django Group model. as your model inherit from the Django model, I will suppose you do something like this:
class GroupExtend(models.Model):
...then your other fields etc.