Django - how to pass a value from <a href> to a view - python

I have a category dropdown list in a template. I don't want to hard code it and write separate list views for each category. So is there a way to pass a value from <a href=""> to a view? I guess self.request.GET.get('category search') never works because there is no <form method="get"> tag. Therefor it always returns None due to the return queryset.none() statement.
home.html:
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
{% if all_categories %}
{% for category in all_categories %}
<a name="category search" href="{% url 'book:category_search' category.id %}"> {{ category }} </a>
{% endfor %}
{% endif %}
</div>
book.urls.py:
urlpatterns = [
.........
path('categories/<int:pk>', views.CategorySearchView.as_view(), name='category_search'),
]
book.views.py:
class CategorySearchView(generic.ListView):
template_name = 'book/search.html'
model = Book
context_object_name = 'book_list'
paginate_by = 100
def get_queryset(self):
queryset = super().get_queryset()
search = self.request.GET.get('category search')
if search:
queryset = Book.objects.filter(categories__id=search)
return queryset
else:
return queryset.none()

Since your URL has <int:pk>,
path('categories/<int:pk>', views.CategorySearchView.as_view(), name='category_search'),
you can access self.kwargs['pk'] in the view:
def get_queryset(self):
queryset = super().get_queryset()
search = self.kwargs.get('pk')
if search:
queryset = Book.objects.filter(categories__id=search)
return queryset
else:
return queryset.none()
As an aside, you would use self.request.GET if pk was in the querystring, e.g. /categories/?pk=1. When you submit a form with method=GET, the form fields will be included in the querystring. But you can also have a link with a querystring, e.g. my link.

Related

Django 'Model matching query does not exist.' error

I have a three simple models. User, Restaurant, Menu. A single user can have many restaurants, and each restaurant can have many Menus. I am using the generic Django CreateView for the Posting of the Restaurants and Menus to their respective models. This works for The restaurants, But fails for the Menus and I get a "Restaurant matching query does not exist" ERROR. I feel that it is a URL issue because I can reach the template if I input it manually, but fails when i use the button.
VIEWS.py
#login_required
def profile(request, pk):
user = CustomUser.objects.get(pk=pk)
if not user.id == request.user.pk:
raise PermissionDenied()
else:
context = {
'user': user,
'rests': Restaurant.objects.filter(account=request.user).order_by('-date'),
'countRests': Restaurant.objects.filter(account=request.user).count(),
}
return render(request, 'restaurants/profile.html',context)
class RestCreateView(LoginRequiredMixin, CreateView):
template_name = 'restaurants/makeRestaurant.html'
form_class = RestCreateForm
def form_valid(self, form):
form.instance.account = self.request.user
return super().form_valid(form)
def RestMenus(request, pk, name):
rest = Restaurant.objects.get(name=name)
user = CustomUser.objects.get(pk=pk)
if not user.id == request.user.pk:
raise PermissionDenied()
else:
context = {
'rest': rest,
'user': user,
'menus': Menu.objects.filter(restaurant=rest).order_by('-uploadDate'),
'countMenus': Menu.objects.filter(restaurant=rest).count(),
}
return render(request, 'restaurants/menus.html',context)
class MenuCreateView(LoginRequiredMixin, CreateView):
template_name = 'restaurants/makeMenu.html'
form_class = MenuCreateForm
def form_valid(self, form):
form.instance.restaurant = self.request.restaurant
return super().form_valid(form)
URLS.py
urlpatterns = [
path('', home, name='home'),
path('profile/<int:pk>/',profile, name='profile'),
path('profile/<int:pk>/createRest/', RestCreateView.as_view(), name='createRest'),
path('profile/<int:pk>/restaurant/<str:name>',RestMenus, name='RestDetail'),
path('profile/<int:pk>/restaurant/<str:name>/createMenu/', MenuCreateView.as_view(), name='createMenu'),
]
FORMS.py
class RestCreateForm(forms.ModelForm):
class Meta:
model = Restaurant
fields = [
'name'
]
class MenuCreateForm(forms.ModelForm):
class Meta:
model = Menu
fields = [
'name',
'menuFile'
]
Template (Menus.html)
<section id="menusdash">
<div class="container">
<br>
<h3>{{ rest.name }}'s Menus</h3>
<br>
<div class="row">
<div class="col-md-10">
{% if countMenus == 0 %}
<p>You have no Menus</p>
{% else %}
{% for menu in menus %}
<div class="card" style="width: 18rem;">
<div class="card-body">
<h5 class="card-title">Menu Name: {{ menu.name }}</h5>
View Menu
View QR Code
Edit
</div>
</div>
{% endfor %}
{% endif %}
</div>
<div class="col-md-2">
<button>Make a Menu</button>
</div>
</div>
</div>
</section>
The URL seems to drop the str:name/ for the menu create template. Is it possible to pass a second argument using a CreateView, SO that i can have the PK and String:name in the url?

object is not shown in CategoryView

I wrote in top.html
<div>
<a href="#">
Category
</a>
<div>
{% for category in category_content %}
<a href="{% url 'category' category.name %}">
{{ category.name }}
</a>
{% endfor %}
</div>
</div>
in category.html like
<div>
{% for content in queryset %}
<h2>{{ content.title }}</h2>
SHOW DETAIL
{% endfor %}
</div>
When I put in top.html,no error happens and page is shown but no <h2>{{ content.title }}</h2> is shown here.
I wrote in views.py
class CategoryView(ListView):
model = Category
template_name = 'category.html'
def get_queryset(self):
category_name = self.kwargs['category']
self.category = Category.objects.get(name=category_name)
queryset = super().get_queryset().filter(name=self.category)
return queryset
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['categories'] = Category.objects.all()
return context
def category(self,pk):
queryset = self.base_queryset()
category = self.kwargs.get("category")
queryset = queryset.filter(category__name=category)
return queryset
in urls.py
urlpatterns = [
path('top/', views.top, name='top'),
path('category/<str:category>/',views.CategoryView.as_view(), name='category'),
]
in models.py
class Category(models.Model):
name = models.CharField(max_length=100)
created_at = models.DateTimeField(auto_now_add=True)
class Index(models.Model):
title = models.CharField(max_length=100)
text = models.TextField()
category = models.ForeignKey(Category, on_delete=models.CASCADE)
I want to show list of Index data that are by filtered Category in my db in category.html .For example, it is my ideal system ,when I put Python link in top.html,content.title which is related with Python is shown in category.html .But now no content.title is shown.What is wrong in my code?How should I fix this?
You shoud add filtered Index list to the context first:
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['categories'] = Category.objects.all()
context['index_list'] = Index.objects.filter(category=self.category)
return context
Now index_list will be available inside template and you can do this:
<div>
{% for content in index_list %}
<h2>{{ content.title }}</h2>
SHOW DETAIL
{% endfor %}
</div>

Django template filter queryset

I'm new in django.
I has a django application where stores products categorized by 'X' and 'Y'.
views.py
...
class CartListView(ListView):
template_name = 'checkout/list.html'
context_object_name = 'product_list'
def get_queryset(self):
return Product.objects.filter(category__slug='X') | Product.objects.filter(category__slug='Y')
def get_context_data(self, **kwargs):
context = super(CartListView, self).get_context_data(**kwargs)
context['minicurso'] = get_object_or_404(Category, slug='X')
context['pacotes'] = get_object_or_404(Category, slug='Y')
return context
...
In my views.py I filter this products by your categories slug.
The problem is, I'm trying to render the products in category 'X' on top the page and the products in category 'Y' down with a text between them. How I can do this?
list.html
{% for category in product_list %}
{{ category.name }}
{% endfor %}
<p>
Any text
</p>
{% for category in product_list %}
{{ category.name }}
{% endfor %}
First off, you should use IN operator over | when populating the filtered queryset:
def get_queryset(self):
return Product.objects.filter(category__slug__in=["X", "Y"])
Secondly, you can't filter queryset by any field in the template unless you write a custom template tag which does that. However, it defeats the purpose of separation of presentation code from data logic. Filtering models is data logic, and outputting HTML is presentation. Thus, you need to override get_context_data and pass each queryset into the context:
def get_context_data(self, **kwargs):
context = super(CartListView, self).get_context_data(**kwargs)
context['minicurso'] = get_object_or_404(Category, slug='X')
context['pacotes'] = get_object_or_404(Category, slug='Y')
context["x_product_list"] = self.get_queryset().filter(category=context['minicurso'])
context["y_product_list"] = self.get_queryset().filter(category=context['pacotes'])
return context
Then you can use them in the template:
{% for category in x_product_list %}
{{ category.name }}
{% endfor %}
...
{% for category in y_product_list %}
{{ category.name }}
{% endfor %}

Using Taggit with custom view Django

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/

Paginate Django formset

I have a model formset that I want to display 10 forms at a time using Django's Paginator, but it can't be done like paginator = Paginator(formset, 10). What's the correct way to do this, if there is a way?
This is a generic example of the solution I found to my problem:
In the forms.py file:
class MyForm(ModelForm):
class Meta:
model = MyModel
fields = ('description',)
In the views.py file:
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
FormSet = modelformset_factory(MyModel, form=MyForm, extra=0)
if request.method == 'POST':
formset = FormSet(request.POST, request.FILES)
# Your validation and rest of the 'POST' code
else:
query = MyModel.objects.filter(condition)
paginator = Paginator(query, 10) # Show 10 forms per page
page = request.GET.get('page')
try:
objects = paginator.page(page)
except PageNotAnInteger:
objects = paginator.page(1)
except EmptyPage:
objects = paginator.page(paginator.num_pages)
page_query = query.filter(id__in=[object.id for object in objects])
formset = FormSet(queryset=page_query)
context = {'objects': objects, 'formset': formset}
return render_to_response('template.html', context,
context_instance=RequestContext(request))
You need to create the formset with the objects in the present page, otherwise, when you try to do formset = FormSet(request.POST, request.FILES) in the POST method, Django raises a MultiValueDictKeyError error.
In the template.html file:
{% if objects %}
<form action="" method="post">
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset.forms %}
{{ form.id }}
<!-- Display each form -->
{{ form.as_p }}
{% endfor %}
<input type="submit" value="Save" />
</form>
<div class="pagination">
<span class="step-links">
{% if objects.has_previous %}
Previous
{% endif %}
<span class="current">
Page {{ objects.number }} of {{ objects.paginator.num_pages }}
</span>
{% if objects.has_next %}
next
{% endif %}
</span>
</div>
{% else %}
<p>There are no objects.</p>
{% endif %}
A more elegant solution is to set ordered=True on the Page object so that it can be passed to a ModelFormSet.
Here is an example:
forms_per_page = 10
current_page = 1
ModelFormSet = modelformset_factory(MyModel, form=MyForm)
queryset = MyModel.objects.all()
paginator = Paginator(queryset, forms_per_page)
page_object = paginator.page(current_page)
page_object.ordered = True
form = ModelFormSet(queryset=page_object)
This is more efficient than the accepted answer because avoids the second database query that takes place in the line:
page_query = query.filter(id__in=[object.id for object in objects])
More correct way to use this
...
formset = FormSet(queryset=page_query.object_list)
...
The problem here is that you're using brands (a Page) in a context that's expecting a QuerySet. So, we need that damn QuerySet. You are in right way, but a lot of code.
In source code we have:
class Page(collections.Sequence):
def __init__(self, object_list, number, paginator):
self.object_list = object_list
self.number = number
self.paginator = paginator
...
So, our queryset in self.object_list attribute and just use it!
formset = SomeModelFormSet(queryset=objects.object_list)
Agree with Elrond Supports Monica. Fake attribute is interesting way to resolve the ordering error (Cannot reorder a query once a slice has been taken.)
But it can be fixed in one line also
queryset = queryset.order_by(Entry._meta.pk.name)
This fake ordering is need for avoid error in django.form.modelsBaseModelFormSet(BaseFormSet).get_queryset(): line #640
that make artificial ordering by pk but it impossible after slicing
(LIMIT-ations in SQL )
More detailed example
queryset = Entry.objects.all()
queryset = queryset.order_by(Entry._meta.pk.name)
paginator = Paginator(object_list=queryset, per_page=10)
page_obj = paginator.get_page(request.GET.get('page'))
EntryFormSet = modelformset_factory(Entry, EntryForm, extra=0)
entryformset = EntryFormSet(queryset=page_obj.object_list)

Categories

Resources