Site wide navbar search using class based views - python

I have a search form in the navbar that is obviously available site wide, however, search only works on the home page because thats the view the search code is written. I figured class based views were the answer such that I could easily add the search functionality as a mixin or something of the kind.
Bottomline: I'm having trouble abstracting the search functionality in a class based DRY
The search form returns www.site.com/?q=search. I need to grab q from the GET request and return that info to my search.html page.
This is my code as it stands
views.py
class HomeListView(ListView, Searchmixin):
model = Part
queryset = Part.objects.order_by('-creDate')
template_name = 'parts/index.html'
paginate_by = 20
is_paginated = True
search.py
class SearchMixin(object):
def sindex(self):
query = self.request.GET.get('q')
return httpResponse(query)
def perf(self, query):
if query is not None:
pollist = self.objects.filter(
Q(project__name__icontains=query) |
Q(owner__icontains=query) |
Q(description__icontains=query) |
Q(pnumber__icontains=query)
)
return render(request, 'parts/search.html', pollist)

Nice job creating your first site. On every view that you would like to have a functional search bar, you should include the SearchMixin that you've created.
One important thing that will save you some trouble down the road is to make sure that the generic class-based views go on the right-hand side of any mixins that you've created -- i.e. class HomeListView(Searchmixin, ListView). That's because Python has an order in which it builds up the class based on what it's inheriting from. Sometimes there could be conflicts if the default ones get overwritten before they're supposed to.

Instead of using SearchMixin and inheriting everywhere, I suggest creating a SearchView, assigning it some URL and returning search results from there.
# urls.py
urlpatterns = [
url(r'/search/', SearchView.as_view(), name='search'),
...
]
# views.py
class SearchView(ListView):
paginate_by = 20 # to show 20 search results per page
def get_template_names(self):
return ['parts/search.html']
def get_queryset(self):
query = self.request.GET.get('q')
pollist = YourModel.objects.all()
if query:
pollist = pollist.filter(
Q(project__name__icontains=query) |
Q(owner__icontains=query) |
Q(description__icontains=query) |
Q(pnumber__icontains=query)
)
return pollist
Now in your JavaScript, you can perform asynchronous GET request to http://www.your_site.com/search/?q=some_search_term, and fetch results and append them in DOM. Hope this helps.

Related

django paginationated first page content different to root page content (with cache)

I am paginating content based on a random queryset and I seem to be getting inconsistent results between the paginated first page ?page=1 and the root page itself.
So, I have my ListView like so:
class Some1_ListView(ListView):
model = Some_Model
template_name = "test1.html"
paginate_by = 12
context_object_name = "test1"
queryset = Some_Model.objects.all().order_by('?')[:24]
My urls is like so:
urlpatterns = [
path('test1/', cache_page(500)(Some1_ListView.as_view()), name="test1" ),
]
and the template is paginated according to the django docs, nothing special. Now, when I go to:
localhost/test1
I get the first 12 objects. Now, When I move to the next page, onbiously, my url becomes:
localhost/test1/?page=2
This renders fine aswell. Now, when I go back to
localhost/test1/?page=1
I see that the results are not the same as localhost/test1/
So, localhost/test1/?page=1 != localhost/test1/ Since I was caching the URL, I was expecting these two to be the same.
Can someone enlighten me as to why this is? and how do I get aorund it? Id like both these pages to show the same content (from cache).

Django Wagtail: get children of page using custom method in django regular view

I am playing with Wagtail 2.6.1 and I got in confusing problem. I need to work with Page model in vanilla Django view. I want to get children of BlogPage, so I made custom method, which should return all children pages. Method works, but only in template view. When I access it in django view, it returns empty queryset. I really don't understand why.
My models.py
class BlogPage(Page):
template = "blog/blog.html"
max_count = 1
subpage_types = ['blog.BlogPostPage','blog.PostAdvancedPage']
promote_panels = Page.promote_panels + [
FieldPanel('menu_order'),
]
def getChildren(self):
children = self.get_children().live().all()
return children
def get_context(self, request):
context = super(BlogPage, self).get_context(request)
context['categories'] = BlogCategory.objects.order_by('name').all()
context['blog_posts'] = self.get_children().live().order_by('-first_published_at').all()
return context
class Meta:
verbose_name = "Blog"
verbose_name_plural = "Blogs"
my views.py
from .models import BlogPage
def post_filter(request):
if request.method == 'POST':
posts = BlogPage().getChildren() # this return empty queryset
print (posts)
but when I render this method in template blog.html it works:
<div class="section py-9">
{{self.getChildren}}
</div>
it successfully render all children pages in template:
<PageQuerySet [<Page: Lorem Ipsum 01>,<Page: Lorem Ipsum 02>]>
Please note that I am normally using get_context method to render all posts in my template, I just tried render this method (getChildren) in template to check if that's working.
The line BlogPage().getChildren() means "create a new BlogPage instance in memory, and fetch its children". Naturally, since it's only just been created, it won't have any child pages, and so this returns an empty queryset.
For the post_filter view to do something useful, you'll need to specify an existing BlogPage instance for it to call getChildren on. You haven't mentioned how post_filter is going to be used, so I don't know where this information should come from - but one possibility is to pass it as a parameter in the URL, in which case post_filter will look like this:
def post_filter(request, page_id):
if request.method == 'POST':
posts = BlogPage.objects.get(id=page_id).getChildren()
print (posts)

Django Class Based Views keep url parameters in session

I have a django listview working fine.
Its receive url parameters to filter data.
Its paginated.
Now, I want to maintain these data along the user session. (page number and url parameters).
Example:
I'm in the products list view.
I search for 'foo'
I select page 2
And then, I click in any product detail.
The page will redirect to detail view.
When I return to product list view, I whant to keep the search argument 'foo' and selected page 2.
What is the better way to do this?
I'm using Django 2.0.6
Models.py
class Product(models.Model):
name= models.CharField(_('name'), max_length=150)
price = models.DecimalField(max_digits=10, decimal_places=2, default=0.0)
Views.py
class ProductList(ListView):
model = Product
paginated_by = 10
def get_queryset(self):
queryset = Product.objects.all()
name = self.request.GET.get('name', None)
if name:
queryset = queryset.filter(name__icontains=name)
return queryset
Urls.py
path('products/', views.ProductList.as_view(), name='product_list'),
For this you have to put the URL as a get request so that you can fetch the get values form the URL and use them in your filter to maintain your selection like:
url/?variable=value
Then in your Django view, you can access this by request.GET.get('variable') and pass this as the context in your HTML render page then use that variable in your filter selection.
Setting variable in session:
For setting the variable in the session you can set it by:
request.session['variable'] = 'value'
and this value can be retrieve by:
if 'variable' in request.session:
variable1 = request.session['variable']
You can refer this docs.
One common trick I use to do this is to use GET parameters and save directly the entire url in session (it saves time compared to saving each individual parameter individually)
class ProductList(ListView):
model = Product
paginated_by = 10
def get_queryset(self):
self.request.session['saved_product_list_url'] = self.request.get_full_path()
....
Then you can use it like this in templates :
product list
Or like this in views :
saved_product_list_url = self.request.session.get('saved_product_list_url')
if saved_product_list_url:
return redirect(saved_product_list_url)
else:
return redirect('product_list')
Also in your filter form you should add a "reset filters" like this :
reset filters

How to change template names in django listview according to url request?

I have 2 templates to render one listview and I am choosing the template according to the request url given by the user. I know that, I can add 2 classes for 2 templates on 2 seperate urls respectively. For example
class MyListView1(generic.ListView):
template_name = 'myapp/list_one.html'
.....
.....
class MyListView2(generic.ListView):
template_name = 'myapp/list_two.html'
.....
.....
But is there a way if I could check the url request inside one class and render the template according to it inside one listview class ? something like
class MyListView(generic.ListView):
if request.path == '/list1'
template_name = 'myapp/list_one.html'
if request.path == '/list2'
template_name = 'myapp/list_two.html'
I know this is not a valid code but just to visualise
Whenever you want to do something dynamic in a generic view, it needs to be in a method. This page shows the methods available for ListViews, and you can see that it includes get_template_names() which should do exactly what you want.
An alternative though would be to have two separate view classes, each defining their own template name, that inherit from a common base class which defines the rest of the shared functionality.
Just pass template from urls.py like
path("/list1",views.MyListView.as_view(template_name="myapp/list_one.html"),name="list1")
path("/list2",views.MyListView.as_view(template_name="myapp/list_two.html"),name="list2")

Django class based view mobile template

I'm setting up Django to use a different template for mobile but I'm not sure how to set it up for Class based views. If I include it in the class like below it throws an error.
class EventList(ListView):
model = Event
paginate_by = 10
context_object_name = 'events'
category = None
area = None
starts = None
ends = None
slug_level = ""
if request.mobile:
template_name = "mobile/mobile.html"
...
I have functions like def get_queryset(self): where to place it so it uses a different template for mobile since request isn't in the class based view
I'm using minidetector in function like so:
#detect_mobile
def home(request, template_name='pages/home.html'):
....
if request.mobile:
return render_to_response('mobile/mobile.html', context)
Mobile and desktop will generally use the same HTTP methods, so CBV is not where you distnguish between these clients.
Instead, you should look at the client's properties such as the user-agent header and set the template name accordingly.
I think the following page gives a great intro:
http://mobiforge.com/design-development/build-a-mobile-and-desktop-friendly-application-django-15-minutes

Categories