I have a model Book and a model Review (with a ForeignKey to Book). I wanted to create a view where you have all the data related to a book (DetailView), and add the functionality of showing and creating reviews. I came up with this code, but don't know whether it is a good practice, maybe I should go for something different:
class BookDetailView(CreateView):
template_name = 'books/book_detail.html'
form_class = ReviewForm
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
slug = self.kwargs.get('slug')
obj = Book.objects.get(slug__iexact=slug)
if get_language() == 'es':
context['reviews'] = obj.review_set.all().filter(language__iexact='es')
else:
context['reviews'] = obj.review_set.all().filter(language__iexact='en')
if len(context['reviews']) == 0:
context['is_empty'] = True
context['object'] = obj
return context
def form_valid(self, form):
obj = form.save(commit=False)
return super().form_valid(form)
And the template:
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<h1>{{object.title}}</h1>
<h2>{% trans "Reviews section" %}</h2>
{% for review in reviews %}
<b>{{review.title}}
{% endfor %}
<h2>Add a review!</h2>
{% include "form.html" with form=form %}
{% endblock content %}
And finally the url: url(r'^(?P[\w-]+)/$', ...)
What do you think?
Thanks for your time!
I did something similar once. But used the DetailView and just added the ReviewForm to the context and added a method to handle post data. Something like this:
class BookDetailView(DetailView):
model = Book
def get_context_data(self, *args, **kwargs):
ctx = super().get_context_data(*args, **kwargs)
language = get_language()
ctx.update({
'reviews': ctx['book'].review_set.all().filter(language__iexact=language),
'form': ReviewForm()
})
return ctx
def post(self, *args, **kwargs):
self.object = self.get_object(self.get_queryset())
form = ReviewForm(self.request.POST)
if form.is_valid():
form.instance.book = self.object
form.save()
return HttpResponseRedirect(self.object.get_absolute_url())
else:
ctx = self.get_context_data(**kwargs)
ctx.update({'form': form})
return self.render_to_response(ctx)
I guess this takes a little more code for handling the form, but the bonus is you can set the related book (the user can't fiddle with that form data).
Note that you don't have to specify template_name because it is automagical correct.
I know this post is a little bit old but it helps me a lot with my issue. Thank you #allcaps
First of all, I would like to mention that the "ReviewForm" should inherit from forms.ModelForm and than we will have a access to "instance" and "save()"
More about ModelForm you can read in django docs Django ModelForms
Related
I have created a custom mixin GetVerboseNameMixin in order to get the verbose name of model fields, and then display these in my html template using a DetailView. However, whenever I try and render the list of verbose names nothing is returned, and I cannot work out why.
Mixin.py:
class GetVerboseNameMixin:
def get_verbose_name(model, fields=[]):
verbose_names = []
for field in fields:
verbose_names.append(str(model._meta.get_field(field)))
return verbose_names
View:
class ShowProfileView(GetVerboseNameMixin, DetailView):
model = Profile
template_name = 'profiles/user_profile.html'
verbose_model_fields = GetVerboseNameMixin.get_verbose_name(model=Profile, fields=['first_name', 'surname', 'date_of_birth', 'phone_number', 'bio', 'gender', 'emergency_contact_name', 'emergency_contact_number'])
def get_context_data(self, *args, **kwargs):
context = super(ShowProfileView, self).get_context_data(*args, **kwargs)
user_profile = get_object_or_404(Profile, id=self.kwargs['pk'])
context["user_profile"] = user_profile
return context
def get_object(self, *args, **kwargs):
obj = Profile.objects.filter(id=self.kwargs['pk']).values('first_name', 'surname', 'date_of_birth', 'phone_number', 'bio', 'gender', 'emergency_contact_name', 'emergency_contact_number') # list of dictionaries
object = obj[0]
return object
Html template:
{% extends "base.html" %}
{% block content %}
<h1>Profile</h1>
<br/><br/>
{% csrf_token %}
<ul>
{% for v in object.values %}
{% for field_name in verbose_model_fields %}
<p>{{field_name}}: {{ v }}</p>
{% endfor %}
{% endfor %}
</ul>
<a href='{% url "profiles:edit_profile" pk=user.profile.id %}'>Edit Profile</a>
{% endblock %}
Even if I just render:
{{ verbose_model_fields }}
In my html file nothing is being displayed. This leads me to think maybe the problem is in my mixin, or perhaps the function is not being called properly?
I do not get how verbose_model_fields is getting passed to the template from the view, and also, I did not find any reference in DetailView documentation. I assume you want to have this custom implementation, if so, then you need to pass along this parameter via context:
class ShowProfileView(GetVerboseNameMixin, DetailView):
...
def get_context_data(self, *args, **kwargs):
context = super(ShowProfileView, self).get_context_data(*args, **kwargs)
user_profile = get_object_or_404(Profile, id=self.kwargs['pk'])
context["user_profile"] = user_profile # redundant implementation, you can get this value by `object` context variable in template
context["verbose_model_fields"] = self.verbose_model_fields # or just call self.get_verbose_name(...) method
return context
Also in this solution I have marked redundant implementation that you do not need to re-implement how to get object, because it is already taken care by DetailView.
So I have two forms.ModelForm for my two models
First:
class TranslatorChoice(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user_id = kwargs.pop('user_id',None)
super(TranslatorChoice, self).__init__(*args, **kwargs)
self.fields['owner'].queryset = Translator.objects.all().filter(owner_id = self.user_id)
owner = forms.ModelChoiceField(queryset = None)
class Meta:
model = Translator
fields = ('owner',)
Second:
class ProfileChoice(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user_id = kwargs.pop('user_id',None)
super(ProfileChoice, self).__init__(*args, **kwargs)
self.fields['login'].queryset = Profile.objects.all().filter(created_by_id = self.user_id)
login = forms.ModelChoiceField(queryset= None, label='Profile')
class Meta:
model = Profile
fields = ('login',)
I've tried writing a view for them but it doesn't work, seems like it just won't save because whenever I hit submit button it just refreshes the page and cleans the fields without redirecting me to needed URL. The model instances in my DB aren't updated either.
Here's the view:
def link_profile(request):
context = {
'form': ProfileChoice(user_id=request.user.id),
'form2': TranslatorChoice(user_id=request.user.id)
}
if request.method == 'POST':
form = ProfileChoice(request.POST)
form2 = TranslatorChoice(request.POST)
if form.is_valid():
login = form.cleaned_data.get('login')
translator = form.cleaned_data.get('owner')
link = Profile.objects.get(login=login)
link.owner = login
link.save(['owner'])
form.save()
form2.save()
return redirect('dashboard')
return render(request, 'registration/link.html', context)
I know also something is wrong is because I am using to many save functions. I just don't have any experience in creating views like that...
Sharing my template:
{% extends 'base.html' %}
{% block content %}
<h2>Add profile</h2>
<form method="post">
{% csrf_token %}
<table>
{{ form.as_table }} {{ form2.as_table }}
</table>
<button type="submit">Link</button>
</form>
{% endblock %}`
And my urls.py part with the view:
url(r'^link/', views.link_profile),
You didn't share your urls.py or the form in your template so it's not clear if the view is being executed, or how you're passing your forms. But, here's something that might work if you're not doing it already.
{{ form.as_table }}
{{ form2.as_table }}
FYI: there are some indentation issues with your code but I'm assuming that that's just something that happened when you pasted it here.
I am trying to get user's profile picture in a template using their username. What I have tried:
templatetags/users_extras.py:
from django import template
from django.contrib.auth.models import User
register = template.Library()
#register.filter(name="get_user", is_safe=True)
def get_user(username):
return User.objects.filter(username=username).first()
My template:
{% load users_extras %}
<img src="{{ username|get_user.profile.image.url }}">
My Profile view:
class Profile(ListView):
model = Post
template_name = "users/profile.html"
context_object_name = 'posts'
ordering = ['-datePosted']
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return Post.objects.filter(author=user).order_by('-datePosted')
def get_context_data(self, **kwargs):
context = super(Profile, self).get_context_data(**kwargs)
context['username'] = self.kwargs['username']
return context
The url for profile page is like path('profile/<str:username>/', Profile.as_view(), name='profile')
But that gives error
Could not parse the remainder: '.profile.image.url' from 'username|get_user.profile.image.url'
How can I fix this error or how can I get user object using username in django template?
You may need to get the result of the filter in a variable.
{% load users_extras %}
{% with username|get_user as user %}
<img src="{{ user.profile.image.url }}">
{% endwith %}
No need for solving things in template. First it is a bad practive. Second there's a better way. Just edit the function get_context_data
def get_context_data(self, **kwargs):
context = super(Profile, self).get_context_data(**kwargs)
context['user_requested'] = get_object_or_404(User,
username=self.kwargs.get('username'))
return context
I am pretty new to Python and Django, but what I am trying to do is to use a Crispy Form in one of my template that is loaded with a ListView type of view. I usually load all of my other templates from a UpdateView which provide everything Crispy need already. I can't change the type of view here so I have to stick with the ListView but when I try to load the form, Crispy can't find it. I don't know how to provide the form to the template manually.
Here is what I have so far:
My template:
{% extends "base.html" %}
{% load crispy_forms_tags %}
{% load i18n %}
{% load tz %}
{% block content %}
{% crispy form %}
<!-- template content -->
{% endblock %}
My form.py
class UserBirthdayForm(forms.ModelForm):
birth_day = forms.IntegerField(required=True, localize=True, label=_('*My birth day'))
birth_month = forms.IntegerField(required=True, localize=True, label=_('*My birth month'))
birth_year = forms.IntegerField(required=True, localize=True, label=_('*My birth year'))
def __init__(self, *args, **kwargs):
super(UserForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.layout = Layout(
Div(
Div('birth_day', css_class='col-sm-4'),
Div('birth_month', css_class='col-sm-4'),
Div('birth_year', css_class='col-sm-4'),
css_class='row'
),
Submit('save', _('update'), css_class='pull-right'),
)
class Meta():
model = User
fields = ("birth_day", "birth_month", "birth_year")
My view.py:
class MissionList(LoginRequiredMixin, ListView):
form_class = UserBirthdayForm
def get_queryset(self):
#some other stuff
return queryset
def get_context_data(self, **kwargs):
#some other stuff
return context
Here is the error I get from Django when trying to access the page:
VariableDoesNotExist: Failed lookup for key [form] in u"[some other stuff]"
In get_context_data create an instance of your form and add it to the context.
def get_context_data(self, **kwargs):
#some other stuff — where you create `context`
context["form"] = UserBirthdayForm()
return context
I have created for interface for users to filter the content from database by values.
Class in View
class FilterCommentsUIView(TemplateView,FormView):
template_name = 'allcomments.html'
form_class = CommentFilterForm
def get_context_data(self, **kwargs):
context = super(FilterCommentsUIView, self).get_context_data(**kwargs)
logger.debug("Entered context data")
logger.debug(self.request.method)
if self.request.method == 'POST':
form = CommentFilterForm(self.request.POST)
loggeer.debug("Entered POST")
if form.is_valid():
logger.debug("Entered is valid")
parenttype = self.request.POST['pchoice']
users = self.request.POST.get('users')
tags = self.request.POST.get('tags')
fdate = self.request.POST.get('fdate')
tdate = self.request.POST.get('tdate')
typeuser = self.request.POST.get('typeuser')
query = self.request.POST.get('query')
userid = User.objects.get(username ='pavan')
comments = Item.objects.all().filter(user = userid.id).order_by('-created')
paginator = Paginator(comments,20)
page = self.request.GET.get('page')
try:
comments = paginator.page(page)
except PageNotAnInteger:
comments = paginator.page(1)
except EmptyPage:
comments = paginator.page(paginator.num_pages)
context['comments']=comments
context['form']=CommentFilterForm
else:
logger.debug("Entered isvalid else")
logger.debug(form.errors)
else:
logger.debug("Entered Else of POST")
form = CommentFilterForm()
comments = Item.objects.all().order_by('-created')
paginator = Paginator(comments, 20)
page = self.request.GET.get('page')
try:
comments = paginator.page(page)
except PageNotAnInteger:
comemnts = paginator.page(1)
except EmptyPage:
comments = paginator.page(paginator.num_pages)
context['comments']=comments
context['form']= form
return context
When i click on submit button it does not enter the POST if section. It always goes to else part. What am i doing wrong ?
Thanks in advance for help.
Pavan
As others have said you have issues with the fact all your code is in get_context_data. In all actuality you don't even need to use get_context_data to accomplish what you are trying to do.
From what I can see you are trying to get comments on the page. Then submit a form back to the page and get a filtered out set of comments returned, and rendered. Here is a view solution that better does what you want except using generic class based views more fully.
class FilterCommentsUIView(FormMixin, ListView):
template_name = 'allcomments.html'
form_class = CommentFilterForm
paginate_by = 20
model = Item
context_object_name = 'comments'
qs_kwargs = {}
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance with the passed
POST variables and then checked for validity.
"""
form_class = self.get_form_class()
form = self.get_form(form_class)
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def get_queryset(self):
qs = super(FilterCommentsUIView, self).get_queryset()
if self.qs_kwargs:
return qs.filter(**self.qs_kwargs)
return qs
def form_valid(self, form):
parenttype = form.cleaned_data['pchoice']
users = form.cleaned_data('users')
tags = form.cleaned_data('tags')
fdate = form.cleaned_data('fdate')
tdate = form.cleaned_data('tdate')
typeuser = form.cleaned_data('typeuser')
query = form.cleaned_data('query')
userid = User.objects.get(username ='pavan')
# put logic here on what to expand into a filter on orm
self.qs_kwargs['user'] = userid.id
self.render_to_response(self.get_context_data())
I haven't run this code, but this is how it should work.
This uses the FormMixin to give us form functionality along with the ListView with your Item class. This takes care of querying for your objects and paginating them, along with a basic get method.
Also the ListView inherits from TemplateResponseMixin which has your template code so you don't need to inherit from TemplateView so in the code above I removed that.
Based on the very nature of the base View class which ListView uses the dispatch method detects whether you are doing a GET, POST, PUT, DELETE, etc... As such it calls the appropriate method on the view. In this case since you are doing a POST it will call the post method in the view. The FormView's implementation of post is this:
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance with the passed
POST variables and then checked for validity.
"""
form_class = self.get_form_class()
form = self.get_form(form_class)
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
Since we aren't using FormView, but are using FormMixin We need to include it.
So all it does is get the form class you set in self.form_class instantiates it, then checks if it is valid. If so calls the form_valid method. Thus we overrode the form_valid method above. Since it passes in the form with it and it has cleaned all the data we can use the normal django form of form.cleaned_data. This helps us with security so we should use that to get back information.
In the last part of the form_valid method we are returning render_to_response. Normally this just redirects to self.success_url, but we want to render the page so we just do a render_to_response and pass it in our context data. We call self.get_context_data() because that is what builds all the data for paginating our comments that we need.
Now comes a bit of the magic. if you notice there is self.qs_kwargs. This is something that is not normally in GCBV's, but something I added for this implementation. Here is where you would put logic to build a dictionary of filters to run in your orm call in get_queryset. We pack it into the dictionary, because in the get_queryset method above we unpack it all, if needed, to do our filtering.
So if you have:
qs_kwargs = {'user': user.id, 'created__gt': somedate}
And you do:
qs.filter(**qs_kwargs) or Item.objects.filter(**qs_kwargs)
That is roughly the same as doing:
qs.filter(user=user.id, created__gt=somedate) or Item.objects.filter(user=user.id, created__gt=somedate)
Finally, if you pay attention to the get_queryset method you notice it returns back all results, like you want, unless qs_kwargs is populated which happens from the form_valid method. So it takes in to account both GET and POST on the page.
Also to keep things simple your html should look something like this:
<html>
<head></head>
<body>
<div>
{% for comment in comments %}
{{ comment }}
{% endfor %}
<ul class="pagination">
{% if page_obj.has_previous() %}
<li>Previous</li>
{% endif %}
{% for pg in paginator.page_range %}
{% if page_obj.number == pg %}
<li class="active">{{ pg }}</li>
{% else %}
<li>{{ pg }}</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next() %}
<li>Next</li>
{% endif %}
</ul>
</div>
<div>
<form action="" method="POST">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="submit" />
</form>
</div>
</body>
</html>
Hope that helps.
This needs to be done in the form_valid() method, not the get_context_data method. get_context_data is just used to provide extra data (aka context) to the template during the request.