Different display of items in view/template depending on model in Django - python

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.

Related

Django: How to autopopulate foreign key with the corresponding model class instance

Working on my first Django project and could use some help. I have 2 models (Decisions, Votes) linked by the foreign key called 'decision'. The template, vote_list.html, shows the user a list of decisions (generated by other users) that are contained in Decisions. The user taps a particular decision and is re-directed to a second template to vote on options pertaining to that decision. How do I autopopulate the foreign key 'decision' in Votes with the corresponding instance of Decision so that the second template, vote_form.html, displays the options for the decision they tapped on? I assume it's coded in views.py (I commented an attempt below that doesn't work), but how might it be done? Thank you!
urls.py
path('vote-list/', views.VoterView.as_view(), name='vote_list'),
path('vote-list/<pk>/vote-form/', views.VoteForm.as_view(), name='vote_form'),
models.py
class Decisions(models.Model):
custom_user = models.ForeignKey(CustomUser,
default=None, null=True,
on_delete=models.SET_NULL)
description = models.CharField(default="",
max_length=100, verbose_name="Decision
Summary")
class Votes(models.Model):
decision = models.ForeignKey(Decisions,
default=None, null=True,
on_delete=models.SET_NULL)
vote = models.CharField(default="", max_length=100,
verbose_name="Your vote")
views.py
class VoteForm(LoginRequiredMixin, CreateView):
model = Votes
form_class = VotingForm
template_name = 'users/vote_form.html'
def post(self, request, *args, **kwargs):
super()
form = self.form_class(data=request.POST)
if form.is_valid():
instance = form.save(commit=False)
# instance.decision = Decisions.description
instance.save()
return redirect('users:vote_list')
forms.py
class VotingForm(forms.ModelForm):
class Meta:
model = Votes
fields = ['vote']
vote_list.html
{% for item in Decisions %}
<tr>
<td>{{ item.description }}</td>
<td><a href="{% url 'users:vote_form' item.id
%}">Vote</a></td>
</tr>
{% endfor %}
vote_form.html
{# trying to display the corresponding
decision description here from vote_list.html # }}
{{ form.vote|as_crispy_field }}
I think this might solve your problem:
Add decision field in the voting form. This will display an option to select for which decision you need to save this Vote for.
If you don't want to allow users to change the Decision, you can mark the field as disabled. See this issue for more details on how to do that. Another alternative is to completely hide the field.
class VotingForm(forms.ModelForm):
class Meta:
model = Votes
fields = ['vote', 'decision']
Add initial value of the decision when instantiating the VotingForm. This will automatically set which decision is selected when displaying the form.
class VoteForm(LoginRequiredMixin, CreateView):
model = Votes
form_class = VotingForm
template_name = 'users/vote_form.html'
# Use this to pass 'pk' to your context in the template
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({'pk': self.kwargs['pk'})
return context
def get_initial(self):
initial = super().get_initial()
initial.update({'decision': self.kwargs['pk']})
return initial
def get_success_url():
# Import reverse from django.urls
return reverse('users:vote_list')
Also, your form should probably be displayed like this in the HTML template: {% crispy form %}. This way all defined fields from the VotingForm class are rendered automatically.
<form method="post" action="{% url 'users:vote_form' pk %}">
{% crispy form %}
</form>

Django/Python - How to show "task" ONLY if the user is author or responsable of "task"

I'm new to python and django and I need some help, please.
What I'm trying to do is to only show a certain "task" if the user is responsable or author of the "task" in question.
I was trying to do that with a if statement in html template:
{% for task in task_list %}
<h2>title - {{task.title}}</h2>
{% endfor %}
{% endif %}
But does not return what I expected since:
{% for task in task_list %}
<h2>author --- {{task.author}}</h2>
<h2>responsable --- {{task.responsable}}</h2>
{% endfor %}
Returns me the same user... I think the problem is that when I refer user.username it goes to the db and returns a query, and when I user {{task.blablabla}} its a string, I'm right? How I can fix that?
models.py:
title = models.CharField(max_length=50)
content = models.TextField(blank=True)
date_created = models.DateTimeField(auto_now_add=True)
due_date = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE, default=User)
responsable = models.ForeignKey(User, on_delete=models.CASCADE, related_name="author", default=User)
STATUS_CHOICES = [('D', 'Done'),('P','Doing'),('N','Not done')]
Status = models.CharField(max_length=1,choices=STATUS_CHOICES, default='N')
IMPORTANCE_CHOICES = [('H', 'High'),('M','Medium'),('L','Low')]
importance = models.CharField(max_length=1,choices=IMPORTANCE_CHOICES, default='M')
DEPARTAMENT_CHOICES = [('D', 'Dev'),('M','Marketing'),('H','Human Resources'),('L','Legal'),('F','Financial'),('O','Others')]
departament = models.CharField(max_length=1,choices=DEPARTAMENT_CHOICES, default='M')
def __str__(self):
return self.title
views.py
def dashboard_taskapp(request):
task = Task.objects.all()
context = {
"task_list": task,
}
return render(request, "task_app/task_dashboard.html", context)
Thanks in advance and kind regards,
So the cleaner and safer solution here is to use Class Based Views (CBV). There is a generic CBV specially created for displaying lists of objects - ListView. To achieve exactly what you did the code would present as follows:
from django.views.generic.list import ListView
class DashboardTaskAppView(ListView):
template_name = "task_app/task_dashboard.html"
# we override this method to
# get the queryset of objects we want to display
# rest of the work will be taken care of by "djnago"
def get_queryset(self):
usr = request.user
return Task.objects.filter(Q(author=usr) | Q(responsible=usr) | Q(public=True))
Now to get the wanted result in template we simply invoke it like this
{% for task in object_list %}
<h2>title - {{task.title}}</h2>
{% endfor %}
when using CBV remember to user path in urls like this
path('your_url/', DashboardTaskAppView.as_view(), name='your_name'),
Please read the docs to discover full Django generic views potential it really speeds up your job and reformatting it later on takes seconds instead of hours.
I hope this is what you want to achieve. Firstly, modify task queryset so it only shows your chosen person's tasks. (You need to import Q for OR contidion)
from django.db.models import Q
def dashboard_taskapp(request):
usr = request.user
task = Task.objects.filter(Q(author=usr) | Q(responsible=usr))
context = {
"task_list": task,
}
return render(request, "task_app/task_dashboard.html", context)
Then there is no need to limit your template so in the end it will look like this
{% for task in task_list %}
<h2>title - {{task.title}}</h2>
... whatever you wanna show here ...
{% endfor %}

Django: Get all objects from a specific user

I have a problem when trying to display all the Announce objects from a user.
My problem is : Consider that I am logged in as a user with an id=1. When I go to /users/1/ it displays all my posts. But the problem is when I want to display all the posts from another user with id=2 by going to /users/2/, it still display all my Announce objects, and not the user with the id=2.
models.py
class Announce(models.Model):
owner = models.ForeignKey('auth.User', related_name='announces')
created_date = models.DateTimeField(auto_now_add=True)
body = models.TextField(max_length=1000)
views.py
class UserAnnouncesList(ListView):
model = Announce
template_name = 'myApp/user_announces_list.html'
context_object_name = 'all_announces_by_user'
def get_queryset(self):
return Announce.objects.filter(owner=self.request.user)
urls.py
urlpatterns = [
url(r'users/(?P<pk>[0-9]+)/$', views.UserAnnouncesList.as_view(), name='user_announces_list'),]
user_announces_list.html
{% extends "account/base.html" %}
{% block content %}
{% for announce in all_announces_by_user %}
<h1>{{announce.user.username}}</h1>
<p>{{announce.body}}</p>
{% endfor %}
{% endblock content %}
Do I have to use some kind of like : Announce.objects.get(pk=???) ?
I appreciate your help!
The request.user is the user that is logged in. You need to use the pk that is passed as url. This is stored in the kwargs dictionary of the listview:
class UserAnnouncesList(ListView):
model = Announce
template_name = 'myApp/user_announces_list.html'
context_object_name = 'all_announces_by_user'
def get_queryset(self):
return Announce.objects.filter(owner=self.kwargs['pk'])

Django ModelForm; User, this field is required

I am having some issues with a (model)form consisting of just a single button. When I try to submit the form this message is displayed:
user
This field is required.
The ModelForm looks like this:
from django.forms import ModelForm
from .models import HulpOproep
class HulpOproepForm(ModelForm):
class Meta:
model = HulpOproep
fields = ['user', ]
The Model looks like this:
class HulpOproep(models.Model):
user = models.ForeignKey(User)
time = models.DateTimeField(auto_now_add=True, verbose_name='Tijd')
def __str__(self):
return '%s %s' % (self.user.username, str(self.time))
def username(self):
return self.user.username
def first_name(self):
return self.user.first_name
def last_name(self):
return self.user.last_name
class Meta:
verbose_name = 'Hulp Oproep'
verbose_name_plural = 'Hulp Oproepen'
The View looks like this:
def verzend_oproep(request):
if request.method == 'POST':
form = HulpOproepForm(request.POST)
if form.is_valid():
oproep = form.save(commit=False)
oproep.user = request.user
oproep.save()
return redirect('portal/index/')
else:
form = HulpOproepForm()
return render(request, 'portal/verzend_oproep.html', {'form': form})
The Template:
{% extends "base.html" %}
{% block head %}
<title>Zorggroep | Hulp Oproep</title>
{% endblock %}
{% block body%}
<h1>Verstuur Hulpoproep</h1>
<form method="POST" class="post-form">{% csrf_token %}
{{ form.as_P }}
{{ form.errors }}
<button type="submit" class="save btn btn-default">Verstuur</button>
</form>
{% endblock %}
The 'user' in the HulpOproep model is a ForeignKey and should be the currently logged in user's User object. I tried to specify this using the line:
oproep.user = request.user
So what should happen is:
Get the current user's 'User' object and use it as the 'HulpOproepForm.user'. This way the 'HulpOproepForm.user' is the 'HulpOproep.user' and a Foreign Key.
I have followed multiple tutorials and have searched around, but I cannot find a solution. I'm sorry if the answer is logical, but I have been using Django for only 5 days and have 1.5 months of programming experience under my belt.
Thank you!
Thanks PatNowak and Radek!
I did not know the form was waiting for user input instead of code input. I managed to fix it by adding exclude to the ModelForm.
class HulpOproepForm(ModelForm):
class Meta:
model = HulpOproep
exclude = ['user', 'time']

Django's Generic Views: how to filter get_queryset based on foreign class attributes?

I've been following Django starter tutorial ( https://docs.djangoproject.com/en/1.8/intro/tutorial05/ )
I decided to make some modifications to test my skills as of now.
Specifically I intend to implement a custom get_queryset for ResultsView generic view.
Something like this:
# views.py
class ResultsView(generic.DetailView):
model = Question
template_name = 'polls/results.html'
def get_queryset(self):
'''
Make sure we are displaying results for choices which have 1+ votes
'''
return Question.objects.get ...
Basically my goal is to return Questions' choices only for choices with at least 1 vote.
So I tried in Django's shell something like this:
# Django shell
q = Question.objects.get(pk=1)
q.choice_set.filter(votes=1)
[<Choice: Not much>]
Here I get question with pk = 1, then filter based on choice_set (of Choice model, whose fk refers to Question model).
I'm trying to figure out how to implement this in my views.py, so that it returns Questions' content (i.e. choices) only for choices with 1+ votes (i.e. displaying all choices with related votes but choices with 0 votes).
Just for completeness here is the actual template (polls/results.html):
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li> {# pluralize used to automatically add "s" for values with 0 or 2+ choice.votes #}
{% endfor %}
</ul>
Vote again?
Models
# models.py
Class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
def __str__(self):
return self.question_text
def was_published_recently(self):
now = timezone.now()
return now - datetime.timedelta(days=1) <= self.pub_date <= now
class Choice(models.Model):
question = models.ForeignKey(Question)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __str__(self):
return self.choice_text
I think __ relations should do you.
Something like this maybe?
def get_queryset(self):
return Question.objects.filter(choices__votes__gte=1)
EDIT:
You actually want to overload get_object. See the definitions of get() and get_object here: https://ccbv.co.uk/projects/Django/1.8/django.views.generic.detail/DetailView/
Specifically, something like:
pk = self.kwargs.get(self.pk_url_kwarg, None)
Choice.objects.filter(question__pk=pk, votes__gte=1)
What you are doing is a little weird because detail view usually works on one object, but this should work.

Categories