I'm learning to work with CBV's and I am currently working on a simple todo list, I want to get specific user data for specific users. The way I am doing this is by overriding the get_context_data() method in my ListView class, but the data is not showing in my template. I have created multiple users and created multiple tasks, but still does not work even though I can access them through the admin page.
Here is my code:
views.py:
class TaskList(LoginRequiredMixin, ListView):
model = Task
template_name = 'app/home.html'
context_object_name = 'tasks'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['tasks'] = context['tasks'].filter(user=self.request.user)
context['count'] = context['tasks'].filter(
complete=False).count()
return context
models.py:
from django.db import models
from django.contrib.auth.models import User
class Task(models.Model):
user = models.ForeignKey(
User, on_delete=models.CASCADE, blank=True, null=True)
title = models.CharField(max_length=150)
description = models.TextField(max_length=500)
complete = models.BooleanField(default=False)
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
# Order by completion
class Meta:
ordering = ['complete']
urls.py:
from django.urls import path
from .views import (
TaskList,
TaskDetail,
TaskCreate,
TaskUpdate,
TaskDelete,
UserLogin,
UserLogout
)
urlpatterns = [
path('login/', UserLogin.as_view(), name='login'),
path('logout/', UserLogout.as_view(), name='logout'),
path('', TaskList.as_view(), name='home'),
path('task-detail/<int:pk>/', TaskDetail.as_view(), name='task-detail'),
path('task-create/', TaskCreate.as_view(), name='task-create'),
path('task-update/<int:pk>/', TaskUpdate.as_view(), name='task-update'),
path('task-delete/<int:pk>/', TaskDelete.as_view(), name='task-delete')
]
home.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>To Do List</title>
</head>
<body>
{% if request.user.is_authenticated %}
<p>{{ request.user }}</p>
Logout
{% else %}
Login
{% endif %}
<hr>
Add Task
<table>
<tr>
<th>
Items
</th>
</tr>
{% for task in tasks %}
<tr>
<td>
{{ task }}
</td>
<td>
View
</td>
<td>
Edit
</td>
<td>
Delete
</td>
</tr>
{% endfor %}
</table>
</body>
</html>
I noticed that when I comment out the line: context['tasks'] = context['tasks'].filter(user=self.request.user) the data does show up in my template but not user specific. What does this mean? Also, adding other context does seem to work, but not when I try getting user specific data. I think I'm missing something small but I can't figure it out.
filter() method or any method of querysets must be applied on models not on empty dictionary keys (eg:tasks).
Try this:
Views.py
class TaskList(LoginRequiredMixin, ListView):
model = Task
template_name = 'app/home.html'
context_object_name = 'all_tasks'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['tasks'] = self.model.objects.filter(user=self.request.user)
context['count'] = self.model.objects.filter(
complete=False).count()
return context
all_tasks will give all objects of Task model as it is ListView which gives all instances, whereas tasks which is in context of get_context_data will give user specific tasks.
Then, you can run loop in template.
Another Best Approach:
If you want to only user specific data in the template app/home.html, so you can simply override the get_queryset inside TaskView in the following way:
views.py
class TaskList(LoginRequiredMixin, ListView):
model = Task
template_name = 'app/home.html'
context_object_name = 'tasks'
def get_queryset(self):
qs = super().get_queryset()
return qs.filter(user=self.request.user)
#for count you have specifiy in get_context_data
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['count'] = self.model.objects.filter(complete=False).count()
return context
Then, without changing your code in the template file, you will be able to access user specific data.
Note: It is better to use actual view name as the suffix while working with class based views, so it will be better if you name it as TaskListView instead of only TaskView.
Related
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>
Hello I am a beginner with the django python framework. I need to display a user's image and bio on a file named user_posts.html in my blog app. I have it where I can access the user's image and bio by looping over the posts for the user. However, I need it so it only displays the bio and the image once. I have a separate profile.html in a users app. In that file, I can do just src="{{ user.profile.image.url }}" and {{ user.profile.bio }} to access the users information but that does not seem to work in my user_posts.html because of the structure of my project. I can't figure out how to tell the for loop to just go over once to access the users information.
users_post.html
{% extends "blog/base.html" %}
{% block content %}
<hr size="30">
<div class="row">
<div class="column left">
{% for post in posts %}
<img style= "float:left" src="{{ post.author.profile.image.url}}" width="125" height="125">
<h5 style="text-align: left;">{{ post.author.profile.bio }}</h5>
{% endfor %}
</div>
views.py
class UserPostListView(ListView):
model = Post
template_name = 'blog/user_posts.html' # <app>/<model>_<viewtype>.html
context_object_name = 'posts'
ordering = ['-date_posted']
paginate_by = 5
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Post.objects.filter(author=user).order_by('-date_posted')
models.py
from django.db import models
from django.contrib.auth.models import User
from PIL import Image
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
image = models.ImageField(default='default.jpg', upload_to='profile_pics')
bio = models.TextField(default='enter bio text here')
def __str__(self):
return f'{self.user.username} Profile'
This is what the problem looks like
Any help is appreciated
Here's how you might do it using get_context_data().
class UserPostListView(ListView):
model = Post
template_name = 'blog/user_posts.html' # <app>/<model>_<viewtype>.html
context_object_name = 'posts'
ordering = ['-date_posted']
paginate_by = 5
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Post.objects.filter(author=user).order_by('-date_posted')
def get_context_data(self, **kwargs):
"""
Add User Profile to the template context.
"""
context = super().get_context_data(**kwargs)
profile_user = get_object_or_404(User, username=self.kwargs.get('username'))
context['profile_user'] = profile_user
return context
You'd then use {{ profile_user.profile.bio }} in your template instead of posts for the user profile info.
This is probably better than getting the first object in posts and getting the user profile information from that object in the case the user has no posts yet (but has a bio).
Note, we're fetching the User object both in get_queryset and in get_context_data so this is not super efficient. There are ways around this but I'll leave it to future editors or you to optimize :)
Probably Not Recommended, But Answering Question
Since you initially were just trying to get the first element, here's how I'd do it.
# Method 1
{% with first_post=posts|first %}
{{ posts.user.profile.bio }}
{{ posts.user.profile.image.url }}
{% endwith %}
# Method 2
{{ posts.0.profile.bio }}
{{ posts.0.profile.image.url }}
I would like to return a very basic, single paragraph from a model but I don't know how or which is the best approach. It's a simple description textField (maindescription) that I would like to be able to change in the future instead of hard-coding it. It has to be simple way but I just could find a good example. Would appreciate any help on how to properly write the view and retrieve it in the template.
model.py
from autoslug import AutoSlugField
from model_utils.models import TimeStampedModel
from time import strftime, gmtime
# Receive the pre_delete signal and delete the file associated with the model instance.
from django.db.models.signals import pre_delete
from django.dispatch.dispatcher import receiver
class Song(models.Model):
author = models.CharField("Author", max_length=255)
song_name = models.CharField("Song Name", max_length=255)
slug = AutoSlugField("Soundtrack", unique=True, always_update=False, populate_from="song_name")
created_date = models.DateTimeField(auto_now_add=True)
updated_date = models.DateTimeField(auto_now=True)
audio_file = models.FileField(upload_to='mp3s/', blank=True)
def __str__(self):
return self.song_name
class MainDescription(models.Model):
main_description = models.TextField()
slug = AutoSlugField("Main Description", unique=True, always_update=False, populate_from="main_description")
def __str__(self):
return self.main_description
view.py
from django.views.generic import ListView, DetailView
from .models import Song, MainDescription
class SongListView(ListView):
model = Song
# Overwrite the default get_context_data function
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
# Add extra information here, like the first MainDescription Object
context['main_description'] = MainDescription.objects.first()
return context
admin.py
from django.contrib import admin
from .models import Song, MainDescription
admin.site.register(Song)
admin.site.register(MainDescription)
urls.py
from django.urls import path
from . import views
app_name = "music"
urlpatterns = [
path(
route='',
view=views.SongListView.as_view(),
name='list'
),
]
song_list.html
{% extends 'base.html' %}
{% block content %}
<div class="container">
<p>{{ main_description.main_description }}</p>
</div>
<ul class="playlist show" id="playlist">
{% for song in song_list %}
<li audioURL="{{ song.audio_file.url }}" artist="{{ song.author }}"> {{ song.song_name }}</li>
{% endfor %}
</ul>
{% endblock content %}
</div>
It looks like you're trying to add extra context to the SongListView
class SongListView(ListView):
model = Song
# Overwrite the default get_context_data function
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
# Add extra information here, like the first MainDescription Object
context['main_description'] = MainDescription.objects.first()
return context
Then in your template you could do something like this
<p>{{ main_description.main_description }}</p>
For more information about that from the docs, you can find it here
I'm trying to create a form in Django template but it is just not showing the fields
here is my files
models.py where i created the desired table
class ReportMessage(models.Model):
sender = models.ForeignKey(UserModel, related_name="report_message_sender", on_delete='CASCADE')
message = models.ForeignKey(Message, on_delete='CASCADE')
created_at = models.DateTimeField(auto_now=True)
reason = models.TextField(max_length=1500)
is_read = models.BooleanField(default=False)
forms.py where i created the form to edit only one field in the table
class ReportMessageForm(forms.Form):
class Meta:
model = ReportMessage
fields = ['reason', ]
views.py where i created the view for the form
#login_required
def report_message(request, pk):
current_user = request.user
reported_message = get_object_or_404(Message, pk=pk)
if request.method == "POST":
report_message_form = ReportMessageForm(request.POST)
if report_message_form.is_valid():
model_instance = report_message_form.save(commit=False)
model_instance.sender = current_user
model_instance.message = reported_message
model_instance.save()
return redirect('report_confirm')
else:
report_message_form = ReportMessageForm()
context = {
'report_message_form': report_message_form,
}
return render(request, 'fostania_web_app/report_message.html', context)
def report_confirm(request):
return render(request, 'fostania_web_app/report_confirm.html')
and urls.py where the urls i used for the views
path('report/messages/<int:pk>/', views.report_message, name="report_message"),
path('report/confirm', views.report_confirm, name="report_confirm"),
and finally that is how i used the form in the html template
{% extends 'fostania_web_app/base.html' %}
{% block content %}
{% load static %}
<form action="" method="post" name="ReportMessageForm" align="right">
{% csrf_token %}
{{ report_message_form }}
<input type="submit" class="btn btn-success" style="width: 100px;" value="إرسال" />
</form>
{% endblock %}
and then all what i see in the html page is the submit button and there is no form labels or input or anything.
In your forms.py if you are not using ModelForm then you have to explicitly declare the fields for the forms
reason = forms.Charfield()
Or you can use ModelForm which inherits from the model you specify.
You should specify the model in the Meta class while using ModelForm.You can also specify required fields from the Model in the fields list in Meta class
Class myform(forms.ModelForm)
Class Meta:
model = your_model_name
fields= [reason,]
Cheers
:)
I think that your problem is in your model form because you are using forms.Form and you need to use forms.ModelForm
class ReportMessageForm(forms.ModelForm):
class Meta:
model = ReportMessage
fields = ['reason', ]
def report_confirm(request):
return render(request, 'fostania_web_app/report_confirm.html', context) #add the context
You need to pass in the "context" so that it shows in the template
I have a Django project where I extended the User to have a Profile using a OneToOneField. I'm using CBV UpdateView which allows users to update their profile. The URL they visit for this is ../profile/user/update. The issue I have is that if a user types in another users name they can edit the other persons profile. How can I restrict the UpdateView so the authenticated user can only update their profile. I was trying to do something to make sure user.get_username == profile.user but having no luck.
Models.py
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.core.urlresolvers import reverse
class Profile(models.Model):
# This field is required.
SYSTEM_CHOICES = (
('Xbox', 'Xbox'),
('PS4', 'PS4'),
)
system = models.CharField(max_length=5,
choices=SYSTEM_CHOICES,
default='Xbox')
user = models.OneToOneField(User)
slug = models.SlugField(max_length=50)
gamertag = models.CharField("Gamertag", max_length=50, blank=True)
f_name = models.CharField("First Name", max_length=50, blank=True)
l_name = models.CharField("Last Name", max_length=50, blank=True)
twitter = models.CharField("Twitter Handle", max_length=50, blank=True)
video = models.CharField("YouTube URL", max_length=50, default='JhBAc6DYiys', help_text="Only the extension!", blank=True)
mugshot = models.ImageField(upload_to='mugshot', blank=True)
def __unicode__(self):
return u'%s' % (self.user)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance, slug=instance)
post_save.connect(create_user_profile, sender=User)
def get_absolute_url(self):
return reverse('profile-detail', kwargs={'slug': self.slug})
Views.py
from django.shortcuts import render
from django.views.generic import DetailView
from django.views.generic.edit import UpdateView
from django.views.generic.list import ListView
from profiles.models import Profile
class ProfileDetail(DetailView):
model = Profile
def get_context_data(self, **kwargs):
context = super(ProfileDetail, self).get_context_data(**kwargs)
return context
class ProfileList(ListView):
model = Profile
queryset = Profile.objects.all()[:3]
def get_context_data(self, **kwargs):
context = super(ProfileList, self).get_context_data(**kwargs)
return context
class ProfileUpdate(UpdateView):
model = Profile
fields = ['gamertag', 'system', 'f_name', 'l_name', 'twitter', 'video', 'mugshot']
template_name_suffix = '_update'
def get_context_data(self, **kwargs):
context = super(ProfileUpdate, self).get_context_data(**kwargs)
return context
Admin.py
from django.contrib import admin
from models import Profile
class ProfileAdmin(admin.ModelAdmin):
prepopulated_fields = {'slug': ('user',), }
admin.site.register(Profile, ProfileAdmin)
Urls.py for Profiles app
from django.conf.urls import patterns, url
from django.contrib.auth.decorators import login_required
from profiles.views import ProfileDetail, ProfileUpdate
urlpatterns = patterns('',
url(r'^(?P<slug>[-_\w]+)/$', login_required(ProfileDetail.as_view()), name='profile-detail'),
url(r'^(?P<slug>[-_\w]+)/update/$', login_required(ProfileUpdate.as_view()), name='profile-update'),
)
Profile_update.html
{% extends "base.html" %} {% load bootstrap %}
{% block content %}
{% if user.is_authenticated %}
<h1>Update your profile</h1>
<div class="col-sm-4 col-sm-offset-4">
<div class="alert alert-info alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<strong>Heads up!</strong> Other users can find you easier if you have a completed profile.
</div>
<form enctype="multipart/form-data" method="post" action="">{% csrf_token %}
{{ form|bootstrap }}
<input class="btn btn-default" type="submit" value="Update" />
</form>
</div>
{% else %}
<h1>You can't update someone elses profile.</h1>
{% endif %}
{% endblock %}
How about something like this:
from django.contrib.auth.views import redirect_to_login
class ProfileUpdate(UpdateView):
[...]
def user_passes_test(self, request):
if request.user.is_authenticated():
self.object = self.get_object()
return self.object.user == request.user
return False
def dispatch(self, request, *args, **kwargs):
if not self.user_passes_test(request):
return redirect_to_login(request.get_full_path())
return super(ProfileUpdate, self).dispatch(
request, *args, **kwargs)
In this example, the user is redirected to default LOGIN_URL. But you can easily change it . to redirect user to their own profile.
To avoid access to data unrelated to the connected user when using Class Based View (CBV), you can use Dynamic filtering and define queryset instead on model attributes.
If you have a book.models with a ForeignKey (named user here) on auth.models.user you can easily restrict acces like this :
# views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import ListView
from books.models import Book
class BookList(LoginRequiredMixin, ListView):
def get_queryset(self):
return Book.objects.filter(user=self.request.user)
See more explanation in the documentation about CBV - Viewing subsets of objects
Specifying model = Publisher is really just shorthand for saying queryset = Publisher.objects.all(). However, by using queryset to define a filtered list of objects you can be more specific about the objects that will be visible in the view.
[…]
Handily, the ListView has a get_queryset() method we can override. Previously, it has just been returning the value of the queryset attribute, but now we can add more logic.
The key part to making this work is that when class-based views are called, various useful things are stored on self; as well as the request (self.request) this includes the positional (self.args) and name-based (self.kwargs) arguments captured according to the URLconf.
your template.html:
{% if request.user.is_authenticated and profile.user == request.user %}
your form
{% else %}
u cannot edit that profile - its not yours...
{% endif %}