Displaying content from 2 models on a single Django template - python

I am working with my first Django project and I have printed my first template tag using a view onto my page template. I'd like to display some other database values on my page, but repeating the same process seems wrong.
Here are my two models:
class Drink(models.Model):
drink_name = models.CharField(max_length=100)
strength = models.IntegerField(default=0)
def __str__(self):
return self.drink_name
class status(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
health = models.IntegerField(default=100)
strength = models.IntegerField(default=50)
charisma = models.IntegerField(default=50)
And my view:
from django.shortcuts import render
from .models import Drink
from .models import status
# Create your views here.
def index(request):
served_drinks = Drink.objects.order_by('strength')
context = {'served_drinks': served_drinks}
return render(request, 'tavern/index.html', context)
And my template:
Hello {{ user.get_username }}, welcome to the tavern.
<br><br>
<div class="card-panel teal lighten-3">Grab a drink from the bar</div>
{% if served_drinks %}
<ul>
{% for drink in served_drinks %}
<li>{{ drink.drink_name }} with a strength of {{ drink.strength }}</li>
{% endfor %}
</ul>
{% else %}
<p>No drinks are available.</p>
{% endif %}
Which returns:
Hello admin, welcome to the tavern.
Grab a drink from the bar
Water with a strength of 0
Wine with a strength of 3
Ale with a strength of 5
Black Ale with a strength of 9
As you can see I've printed out my served_drinks object in the template, but I also want to print out data from the status model on this page too.
I've tried simply adding {{ status.health }} to the template but this does not work. Should I be adding extra information to the index view? If so, what if I want to show this information on many pages?

You should pass your status instance to the context of the view as well. For example, let's get a first instance of status model in database:
def index(request):
served_drinks = Drink.objects.order_by('strength')
status = status.objects.all().first()
context = {'served_drinks': served_drinks, 'status': status}
return render(request, 'tavern/index.html', context)
Now, you can use {{ status.health }} in your template.
Also, try to start your model names with capital letters. So, model Status is better than status. It's just good practices.

Since you're using the render shortcut, you can add whatever you want to the context dict:
from django.contrib.auth.decorators import login_required
#login_required
def index(request):
served_drinks = Drink.objects.order_by('strength')
context = {
'served_drinks': served_drinks,
'status': Status.objects.get(user=request.user),
}
return render(request, 'tavern/index.html', context)
Then you can use {{ status.health }} in the template. Note the login_required decorator is used so that only logged-in users can access the view, otherwise the Status.objects.get() would fail for anonymous users. I would also rename your model from status to Status, so that you do not confuse the Status model with status instances.
However, you don't actually need to add status to the context in this case. You can follow the one-to-one field backwards from user to status:
{{ user.status.health }}

Related

For loop in django retrieving all users instead of one and M2M field data output

I have 2 Issues in Django:
Below are my codes, whenever I am using For loop in my Template, it is retrieving not one user data, but all users/profile data. but when I use request.user.profile.first_name only then it works, but I am in a situation where I need to retrieve data from the many-to-many fields and I have to use For loop, which brings me to my 2nd question
I need to use For loop in the many-to-many field to retrieve the data, but it is fetching for all users, please can you correct me where I am doing wrong? or is there an alternate method to retrieve data without using for loop (one user/profile only post-logging-in)
models.py
class Department(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
email = models.CharField(max_length=200)
departments = models.ManyToManyField(Department)
views.py
def profilepage(request):
profiles = Profile.objects.all()
departments = Department.objects.all()
context = {
'profiles': profiles,
'departments': departments
}
return render(request, 'profile.html', context)
profile.html
{% for profile in profiles %}
First Name: {{profile.first_name}}
{% for department in departments %}
Department: {{department.name}}
{% endfor %}
{% endfor %}
You can access the departments of a Profile with:
{% for profile in profiles %}
First Name: {{ profile.first_name }}
{% for department in profile.departments.all %}
Department: {{ department.name }}
{% endfor %}
{% endfor %}
In order to boost performance, you can use .prefetch_related(…) [Django-doc] to fetch all the related departments with one extra query, instead of one query per profile:
def profilepage(request):
profiles = Profile.objects.prefetch_related('departments')
context = {
'profiles': profiles
}
return render(request, 'profile.html', context)
(…) I use request.user.profile.first_name.
You can pass a single profile to the template with:
from django.contrib.auth.decorators import login_required
#login_required
def profilepage(request):
profile = request.user.profile
context = {
'profile': profile
}
return render(request, 'profile.html', context)
and then render it without enumerating over the profiles so without a {% for ... %} loop:
First Name: {{ profile.first_name }}
{% for department in profile.departments.all %}
Department: {{ department.name }}
{% endfor %}
Note: You can limit views to a view to authenticated users with the
#login_required decorator [Django-doc].

Placing items below category in template

So I wrote a small shopping list application using Django. Users may enter their desired items which are stored in a database alongside a category they reside in to make the list presented to the user more clean and giving them (the users) a good overview of what they are going to buy.
The goal is to show the user a list which is categorized, something like this:
VEGETABLES
Paprika
Tomatoes
VARIOUS
Toothpaste
Toilet Paper
And so on. I have like five categories saved in my database and users may choose one corresponding category once they add an item to the list below which the item will be displayed in the list.
These are my database models:
from django.db import models
class Category(models.Model):
name = models.CharField(max_length=20)
tag = models.CharField(max_length=2)
def __str__(self):
return self.name
class Item(models.Model):
text = models.CharField(max_length=40)
count = models.CharField(max_length=100)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
complete = models.BooleanField(default=False)
def __str__(self):
return self.text
this is my views.py
def items(request):
item_list = Item.objects.order_by('id')
categories = Category.objects.all()
form = ItemForm()
context = {'item_list' : item_list, 'form' : form, 'categories' : categories}
return render(request, 'buyit/index.html', context)
and this is my index.html (very basic, stripped off all the css and html stuff):
{% for category in categories %}
<h4>{{ category.name }}</h4>
{% for item in category.item_set.all() %}
{{ item.text }}
{% endfor %}
{% endfor %}
I got this functions from the Jinja2 template from a code snippet and it might be, that I didn't understand it correctly.
However, the debugger tells me:
TemplateSyntaxError at /
Could not parse the remainder: '()' from 'category.item_set.all()'
Any hints on what I am doing wrong?
Simply displaying the categories worked fine but after adding
{% for item in category.item_set.all() %}
{{ item.text }}
{% endfor %}
things crashed.
Any help is highly appreciated!
Thanks in advance!
You can't use () in django templates. Just remove them to call the function / method.
From https://docs.djangoproject.com/en/3.1/ref/templates/language/#variables
Behind the scenes
Technically, when the template system encounters a dot, it tries the following lookups, in this order:
Dictionary lookup
Attribute or method lookup
Numeric index lookup
If the resulting value is callable, it is called with no arguments. The result of the call becomes the template value.
try this:
views.py:
def items(request):
commoditys = {category: Item.objects.filter(category=category) for category in Category.objects.all()}
form = ItemForm()
context = {'commoditys' : commoditys, 'form' : form}
return render(request, 'buyit/index.html', context)
index.html:
{% for category, commoditys in commoditys.items %}
<h3>{{ category.name }}</h3>
{% for commodity in commoditys %}
{{ commodity.text }}
{% endfor %}
{% endfor %}
Change the Category variable to a dictionary, where the Key of each item is the Category object, and its corresponding value is the item Queryset it belongs to

Django ModelForm Not Saving to DB

I'm new to Django and having a hard time figuring out how to get a model form to save to my db. I've been following some tutorials/books and spending a lot of time on SO and I just can't figure this piece out. The book examples i'm following is creating an IMDB type website where a user can vote movie quality (changed to games for my example).
python v. 3.6.7,
django v. 2.1.3,
postgres v. 2.2.2
Here is the model i'm trying to store and the associated manager
class VoteManager(models.Manager):
def get_vote_or_unsaved_blank_vote(self, game, user):
try:
vote = Vote.objects.get(game=game, user=user)
return vote
except self.model.DoesNotExist:
vote = Vote(game=game, user=user)
return vote
class Vote(models.Model):
objects = VoteManager()
value = models.FloatField()
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
game = models.ForeignKey(Game, on_delete=models.CASCADE,)
voted_on = models.DateTimeField(auto_now=True)
class Meta:
unique_together = ('user', 'game')
Now the model form I am trying using to try and store this
class VoteForm(forms.ModelForm):
user = forms.ModelChoiceField(widget=forms.HiddenInput, queryset=get_user_model().objects.all(), disabled=True)
game = forms.ModelChoiceField(widget=forms.HiddenInput, queryset=Game.objects.all(), disabled=True)
value = forms.FloatField()
class Meta:
model = Vote
fields = ('user', 'game', 'value')
Template i'm using to display this information.
{% block main %}
<h1>{{ object }}</h1>
<p class="lead">
{{ object.summary }}
</p>
{% endblock %}
{% block sidebar %}
{# rating div omitted #}
<div>
{% if vote_form %}
<form
method="post"
action="{{ vote_form_url }}" >
{% csrf_token %}
{{ vote_form.as_p }}
<button
class="btn btn-primary" >
Vote
</button >
</form >
<h3>Score: {{ object.score|default_if_none:"No score yet!" }}</h3>
{% else %}
<p >Log in to vote for this game</p >
{% endif %}
</div >
{% endblock %}
Finally the views to combine all of these pieces
class GameDetail(DetailView):
queryset = Game.objects.all_with_related_persons_and_score()
def post(self, request, *args, **kwargs):
return redirect('core:CreateVote', game_id=kwargs['pk'])
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
if self.request.user.is_authenticated:
vote = Vote.objects.get_vote_or_unsaved_blank_vote(game=self.object, user=self.request.user)
if vote.id:
vote_form_url = reverse('core:UpdateVote', kwargs={'game_id': vote.game.id, 'pk': vote.id})
else:
vote_form_url = reverse('core:CreateVote', kwargs={'game_id': self.object.id})
ctx['vote_form'] = VoteForm(instance=vote)
ctx['vote_from_url'] = vote_form_url
return ctx
class CreateVote(LoginRequiredMixin, CreateView):
form_class = VoteForm
def get_initial(self):
initial = super().get_initial()
initial['user'] = self.request.user.id
initial['game'] = self.kwargs['game_id']
return initial
def get_success_url(self):
print('never called?')
game_id = self.kwargs['game_id']
return reverse('core:GameDetail', kwargs={'pk': game_id})
def render_to_response(self, context, **response_kwargs):
game_id = self.kwargs['game_id']
game_detail_url = reverse('core:GameDetail', kwargs={'pk': game_id})
return redirect(to=game_detail_url)
My guess currently is that I just don't know how to get form from the template, many examples on here create the form when requested, the tutorial i've been following create the form in get_context_data, then pass it off to the template. So i've been struggling to figure out how to save or validate if the form is correct.
Sorry for all the text, but these pieces are so interconnected I wanted to make sure I included everything that would be useful. Displaying and reading from the db seem to work, if I manually go in and add votes through the admin panel I can read them successfully, when trying to write a new score through the vote button nothing happens, below is what the terminal window outputs.
[12/Dec/2018 20:24:34] "GET /game/85 HTTP/1.1" 200 2081
[12/Dec/2018 20:46:49] "POST /game/85 HTTP/1.1" 302 0
[12/Dec/2018 20:46:49] "GET /game/vote/85/create HTTP/1.1" 302 0
[12/Dec/2018 20:46:49] "GET /game/85 HTTP/1.1" 200 2081
Oh maybe the url patterns might be useful.
app_name = 'core'
urlpatterns = [
path('', views.MainPage.as_view(), name='MainPage'),
path('games', views.GameList.as_view(), name='GameList'),
path('game/<int:pk>', views.GameDetail.as_view(), name='GameDetail'),
path('game/vote/<int:game_id>/create', views.CreateVote.as_view(), name='CreateVote'),
path('game/vote/<int:game_id>/update/<int:pk>', views.UpdateVote.as_view(), name='UpdateVote'),
]
Thanks in advance, i've been beating my head against the wall on this one for a while.
Try adding type="submit" to your button:
<button class="btn btn-primary" type="submit">Vote</button >
and then in your CreateView add a form_valid method:
class CreateVote(LoginRequiredMixin, CreateView):
model = Vote
template_name = 'folder/create_vote.html'
form_class = VoteForm
...
def form_valid(self, form):
vote = form.save(commit=False)
game = Game.objects.get(id=game_id)
vote.game = game
vote.save() # You have to save the vote for it to be added to the db.
return HttpResponseRedirect(reverse('redirect_view'))
This had a painful solution, a simple typo. In my GameDetail's get_context_data I setup data for the template to use, one of those fields was the url to post to
ctx['vote_from_url'] = vote_form_url
In the template when I attempted to reference this I misspelled it
action="{{ vote_form_url }}" >
The frustrating thing for me is that this does not come up with an error, in the template if I put anything it defaults to the url of the parent view. I should have noticed that POST was going to the wrong URL, but I wonder if there is a setting or log that would have told me I was referencing something in the template that doesn't exist. Hope this recap helps someone that get stuck in a similar situation.
maybe a bit late and not exactly the same case but i had the same issue with FIELDSET :
PK(id) had to be included in the form.
Discovered this with adding this in my template:
{% for hidden_fields in formset %}
{{ hidden_fields.errors }}
{% endfor %}
Then, when "SUBMIT", this showed :
id
Ce champ est obligatoire.
id
Ce champ est obligatoire.
id
Ce champ est obligatoire...
(champ obligatoire <=> mandatory field)
After adding the hidden TD, data were well saved :
<tbody>
{% for form in formset %}
<tr>
<td class="hidden">{{ form.id }}</td>
<td>{{ form.ean }}</td>
<td>{{ form.used }}</td>
</tr>
{% endfor %}
</tbody>
Not sure it's the best way of doing but it worked for me

Django rendering a db field in the template

Could anyone correct my code?
Background:
The user, once on the 'start.html' template, will enter their name and press submit. Then on the next template, 'game.html', there should be a paragraph tab that contains that users name.
Problem:
I must be writting something incorrectly because the user's name does not render on the 'game.html' template. Or, I could also be storing it wrong. Any suggestions or corrections would be very appreciated!
models.py - fp
from django.db import models
class Player(models.Model):
#first player name
fp_name = models.CharField(max_length=30, default='')
forms.py - I'm not sure if this is actually needed...?
from django import forms
class PlayerInfo(forms.Form):
fp_name = forms.CharField(max_length=30, label='First player name')
views.py
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render, render_to_response
import os
from .forms import PlayerInfo
from .models import Player
def index(request):
return render(request, 'index.html')
def start(request):
if request.method == 'Post':
form = PlayerInfo(request.POST)
if form.is_valid():
obj = Player()
obj.fp_name = form.cleaned_data['fp_name']
return HttpResponseRedirect('/')
else:
form = PlayerInfo()
return render(request, 'start.html')
def game(request):
return render_to_response('game.html', {'obj': Player.objects.all()})
start.html - Meant to submit player one's name
{% extends 'base.html' %}
{% block botRow %}
<form method="post">
{% csrf_token %}
<label for="fp_name">First Player Name</label>
<input id="fp_name" type="text" name="fp_name" maxlength="30" required />
<input type="submit" value="Submit" />
</form>
{% endblock %}
game.html - Meant to render the player one's name
{% extends 'base.html' %}
{% block midRow %}
<p>{{ obj.fp_name }}</p>
{% endblock %}
in your game.html the obj is query set of all Users, so you should walk through the list, look on block for in the docs:
{% extends 'base.html' %}
{% block midRow %}
{% for user in obj %}
<p>{{ user.fp_name }}</p>
{% endfor %}
{% endblock %}
Using User.objects.all() you are getting a Collection of all site's users. It isn't current user. So, the collection doesn't have parameter fp_name. Use request.user to get current logged in user.
Also, there is some redundancy in your code:
Django contains User class with ability to store First Name out of the box. So, you don't need to declare it at all. https://docs.djangoproject.com/en/1.11/ref/contrib/auth/#django.contrib.auth.models.User
There is special class of forms - ModelForm. It helps you to map model's fields to form's fields as fast as possible. (https://docs.djangoproject.com/en/1.11/topics/forms/modelforms/)
There is special class of views - CreateView. It helps you to realize basic logic of model creating. (https://docs.djangoproject.com/en/1.11/ref/class-based-views/generic-editing/#django.views.generic.edit.CreateView)
Forms intended to save your time. So, in templates it's better to use built-in rendering engine of forms, than to declare their fields manually. (https://docs.djangoproject.com/en/1.11/topics/forms/#the-template)
If game.html is permitted only for registered users it's a good idea to use #login_required decorator to restrict access to this part of the site.

Django template: access query_set and non-query_set results in same template

I have a Django app that contains info on schools and states. I want my template to display a list of schools per state and also the name of the state based on the state parameter in the URL. So if a user goes to example.com/vermont/ they will see a list of Vermont schools and a tag that says they're on the "Vermont" page. I can get the list of schools per state to work, but I can't figure out how to simply list the state name in the h1 tag.
Here is my models.py:
from django.db import models
class School(models.Model):
school_name = models.CharField(max_length=200)
location_state = models.CharField(max_length=20)
def __unicode__(self):
return self.school_name
Here is my views.py:
from django.views.generic import ListView
class StateListView(ListView):
model = School
template_name = 'state.html'
context_object_name = 'schools_by_state'
def get_queryset(self):
state_list = self.kwargs['location_state']
return School.objects.filter(location_state=state_list)
And here's my template for state.html:
{% extends 'base.html' %}
{% block content %}
<h1>{{school.location_state }}</h1> [THIS IS THE LINE THAT DOES NOT WORK]
{% for school in schools_by_state %}
<ul>
<li>{{ school.school_name }}</li>
</ul>
{% endfor %}
{% endblock content %}
What am I missing here?
The problem is that the school variable never enters the context. You are only setting the schools_by_state to the context.
To add some extra context you need to override the get_context_data method. This way you can add the location_state from the url parameter:
def get_context_data(self, **kwargs):
context = super(StateListView, self).get_context_data(**kwargs)
context.update({'state': self.kwargs['location_state']})
return context
Then you can use the {{ state }} instead of {{ school.location_state }} in your template.

Categories