I want to display the following two views in a template index.html.
class IndexView(generic.ListView):
template_name = 'items/index.html'
context_object_name = 'popular_items_list'
def get_queryset(self):
return Item.objects.order_by('-rating')[:5]
class tryView(generic.ListView):
template_name = 'items/index.html'
context_object_name = 'latest_items_list'
def get_queryset(self):
return Item.objects.order_by('pub_date')[:5]
Is there a way to combine these two views into one view?
How should I get both query sets displayed on index.html?
Is it possible to send all the Item.objects.all() and filter in the template?
A few questions here, let me answer the first.
You can overwrite get_context_data and add to the context of the template for more items in one view. For example...
class IndexView(generic.ListView):
template_name = 'items/index.html'
context_object_name = 'popular_items_list'
def get_queryset(self):
return Item.objects.order_by('-rating')[:5]
def get_context_data(self, *args, **kwargs):
context = super(IndexView, self).get_context_data(*args, **kwargs)
context['moreItems'] = Item.objects.order_by('pub_date')[:5]
return context
This way you can include multiple query sets on a page/template as required. In this example moreItems would be available in your template along with popular_items_list
In regards to the second question, yes you can pass in the URL arguments and use them to filter the queryset. I suggest reading up on this.
You have two options that I can think of. One option is to get_context_data in the view, which would looks something as follows:
#views.py
class IndexView(generic.ListView):
template_name = 'items/index.html'
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
context['item_by_rating'] = Item.objects.order_by('-rating')[:5]
context['item_by_pub_date'] = Item.objects.order_by('pub_date')[:5]
return context
And then in your template you could access {{ items_by_rating }} and {{ items_by_pub_date }}
The second option would be to sort the objects in the template, which would allow you to define just one context variable in your view and then sort in different ways in the template using the dictsort template filter. This would look something like this:
# views.py
class tryView(generic.ListView):
template_name = 'items/index.html'
def get_queryset(self):
return Item.objects.all()
# index.html
{% for i in object_list|dictsort:"item.pub_date" %}
{{ i.rating }} {{ i.pub_date }}
{% endfor %}
I think I like the 2nd option more just passing one object_list context item and then sorting in the template. But either way should be fine.
Related
I have two models in my django app:
class Person(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=100)
persons = models.ManyToManyField(Person, related_name="books")
Now I need to create a view that will make one query for regex to both of the models, find the ones that are matching and display them in template.
If I do:
class SearchListView(ListView):
queryset = Person.objects.filter(name__icontains="a")
book_queryset = Book.objects.filter(title__icontains="a")
I get an error that ListView accepts only one queryset.
What is the typical solution to such problem?
You need to do something a little bit different here:
class SearchListView(ListView):
queryset = Person.objects.filter(name__icontains="a")
def get_context_data(self, **kwargs):
context = super(SearchListView, self).get_context_data(**kwargs)
context['book_queryset'] = Book.objects.filter(title__icontains="a")
return context
Then in your view you can do somenting like the following:
{% for object in object_list %}
<p>{{object}}</p>
{% endfor %}
{% for object in book_queryset %}
<p>{{object}}</p>
{% endfor %}
The reason why the way you are using is not working is because ListView inherit from MultipleObjectMixin the queryset property and that property is passed to object_list context variable in the template, that happens under the hood and if you want to pass more context variables to the template you need to follow the approach I shared.
This question have been answered before, e.g here: Proper way to handle multiple forms on one page in Django
So before it gets marked as a duplicate. I'll try to explain why its different.
I've got three tables, Project, ProjectUser and User. ProjectUser is a join table to indicate what users belongs to what project.
I'm trying to create a view that lets users update project details (e.g. name of project), and also add users to the project (which is indicated by a dropdown that shows all available users like the standard one for models with foreign keys in the django admin panel). All works fine until I'm trying to pass an id from the views to the formclass and submit.
views.py
class ProjectUpdateView(UpdateView):
form_class = ProjectUpdateForm
second_form_class = ProjectUserAddForm
template_name = 'projects/project_edit.html'
success_url = reverse_lazy('projects:list')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
id_ = self.kwargs.get("id")
project = Project.objects.get(id=id_)
if 'form' not in context:
context['form'] = self.form_class()
if 'form2' not in context:
team = Organization.objects.get(id=project.organization_id)
context['form2'] = self.second_form_class(queryset=team) # <-- here is where I wish to pass a queryset, which fails when trying to submit form2.
context['project_users'] = ProjectUser.objects.filter(project__id=project.id).select_related("project")
context['team'] = Organization.objects.get(id=project.organization_id)
return context
def get_object(self):
id_ = self.kwargs.get("id")
return get_object_or_404(Project, id=id_)
def form_invalid(self, **kwargs):
return self.render_to_response(self.get_context_data(**kwargs))
def form_valid(self, form):
project_id = self.kwargs.get("id")
if self.request.POST.get("form2") == 'Add':
ProjectUser.objects.create(user_id=self.request.POST.get("user"), project_id=project_id)
form.save()
success_url = reverse("projects:edit", args=(project_id,))
return HttpResponseRedirect(success_url)
def post(self, request, *args, **kwargs):
# get the user instance
self.object = self.get_object()
# determine which form is being submitted
# uses the name of the form's submit button
if 'form' in request.POST:
# get the primary form
form_class = self.get_form_class()
form_name = 'form'
else:
# get the secondary form
form_class = self.second_form_class
form_name = 'form2'
# get the form
form = self.get_form(form_class)
# validate
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(**{form_name: form})
projects_edit.html
<form action="{% url 'projects:edit' project.id %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{form.name|as_crispy_field}}
<input name="form" value="Update" type="submit"></input>
</form>
<form action="{% url 'projects:edit' project.id %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{form2.user}}
<input name="form2" value="Add" type="submit"></input>
</form>
forms.py
class ProjectUpdateForm(ModelForm):
class Meta:
model = Project
fields = ["name"]
class ProjectUserAddForm(ModelForm):
def __init__(self, queryset, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['user'].queryset = User.objects.filter(organizations_organizationuser__organization__id=queryset.id) # here is where I wish to pass the id of the queryset from the form class
class Meta:
model = ProjectUser
fields = ["user"]
Rendering the forms works just fine with the desired queryset, but when I try to submit the second form (adding a user to the ProjectUserForm, I just get a
__init__() missing 1 required positional argument: 'queryset' error.
Any ideas on how to solve this? Perhaps I'm making it way more complicated than it should
I have also added a screenshot if it helps: https://imgur.com/a/uqu0UeB
I need to pass id from one template to another template. In template i am iterating over one model
{% for project in all_projects %}
<h3>{{ project.name }}</h3>
{% endfor %}
This going to one template where my url looks like
url(r'^$', views.ProjectsListView.as_view(), name='index'),
url(r'^platforms/$', views.PlatformsIndexView.as_view(), name='platforms'),
url(r'^platforms/nodes/$', views.PlatformsNodesListView.as_view(), name='platforms_list'),
Browser url that i have is http://127.0.0.1:8000/platforms/?project=1
that's ok good. But from second template i need to send third template another parametrs and filters. So how do i can get id of project?
I can not send now project id to third template because i am not iterating over it. How to remember id of project?
views.py
class ProjectsListView(ListView):
template_name = 'project/projects.html'
model = Project
context_object_name = 'all_projects'
class PlatformsIndexView(TemplateView):
template_name = 'project/platforms.html'
class PlatformsNodesListView(ListView):
template_name = 'project/general.html'
model = Platform
context_object_name = 'all_platforms'
def get_queryset(self):
queryset = super().get_queryset()
type_filter = self.request.GET.get('type')
project_filter = self.request.GET.get('project')
if type_filter in [Platform.BACKEND, Platform.ANDROID, Platform.IOS, Platform.FRONTEND]:
queryset = queryset.filter(type=type_filter)
if project_filter:
queryset = queryset.filter(project__id__exact=project_filter)
else:
raise Http404
return queryset
Please explain me.
Thank you in advance
I have a list of items sorted in different ways in a template like this.
views.py
class IndexView(generic.ListView):
template_name = 'items/index.html'
context_object_name = 'item_list'
def get_queryset(self):
return Item.objects.all()
index.html
Latest
{% for movie in movie_list|dictsortreversed:"release_date" %}
{% endfor %}
Popular
{% for movie in movie_list|dictsortreversed:"rating" %}
{% endfor %}
Recent
{% for movie in movie_list|dictsortreversed:"pub_date" %}
{% endfor %}
Now, as you can see, i gave a link to each of these categories(release_date, rating, pub_date) because i want to access them in a template sorted according to the category.
This is what i have done and is not working. How to go about this?
urls.py
url(r'^cat/(?P<cat>\D+)', views.CatView.as_view(), name='cat'),
view.py
class CatView(generic.ListView):
template_name = 'items/cat.html'
context_object_name = 'item_list'
def get_queryset(self):
return Item.objects.all()
def get_context_data(self, *args, **kwargs):
context = super(CatView, self).get_context_data(*args, **kwargs)
context['cat'] = self.kwargs.get('cat')
return context
cat.html
{% for item in item_list|dictsortreversed:cat %}
1) I don't want to allow all kinds of strings to pass through url. How to pass only selected strings, in this case (rating, release_date, pub_date).
2)can some one suggest an elegant way of achieving this? PS: new to programming.
I have a model, that looks something like this:
class Topic(models.Model):
name = models.CharField(max_length=50)
class Vote(models.Model):
user = models.ForeignKey(User, related_name='user_set')
topic = models.ForeignKey(Topic, related_name='topic_set')
score = models.IntegerField(default=0)
class Meta:
unique_together = ("user", "topic")
In my index view, I want to display a list of all the topics. If the user has already voted on the topic, it should show his score. If the user has not voted, it should display a form for the user to vote.
I have extended the model with this method as part of the Topic class:
def user_has_already_voted(self, user):
if not Vote.objects.filter(topic=self.id,user=user.id):
return True
else:
return False
However I don't know whether this is the way to go in Django, since I don't know how to write a view with a corresponding template to perform this task. As of now, I am using a generic IndexView, which looks like this:
class IndexView(generic.ListView):
template_name = 'topics/index.html'
context_object_name = 'latest_topic_list'
def get_queryset(self):
return Topic.objects.order_by('-pub_date')[:5]
use context. In view add:
def get_context_data(self, **kwargs):
context = {
'is_voted' : self.user_has_already_voted(self.request.user),
}
context.update(kwargs)
return super(IndexView, self).get_context_data(**context)
and in template use:
{% if is_voted %}
Show vote results
{% else %}
Show vote form
{% endif %}
You can access your user_has_already_voted in template:
{% if topic.user_has_already_voted %}
Show vote results
{% else %}
Show vote form
{% endif %}
You can use a RedirectView to achieve this:
in your views.py, do something like this (this is code I'm using in a current project, the idea ist quite similar.
class AbstimmungRedirectView(generic.RedirectView):
def get_redirect_url(self, pk):
abstimmung = get_object_or_404(Abstimmung, pk=pk)
if abstimmung.abgeschlossen():
#Die Abstimmung wurde durch Angabe eines Enddatms als "Beendet" markiert
return reverse('my_app:MultipleChoiceFrageResults', args=(int(pk),))
else:
return reverse('my_app:MultipleChoiceFrageDetail', args=(int(pk),))
you should replace my abstimmung.abgeschlossen() by your has_voted() and use the reverse url for the templates you want to show.