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.
Related
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 found multiple answers to this same questions but unfortunately, I can't seem to figure it out :(
The form has a drop-down list for the 'subcategory' field in my model 'PhysicalPart', the values of the 'subcategory' field are updated dynamically upon the form creation (using a 'category' parameter).
Unfortunately, I can't get the drop-down to show all subcategories AND have the one from the database selected at the same time. I can't seem to retrieve the 'short_description' value either from the database.
It used to work before I learned about UpdateView class and decided to use it instead...
Any insight on how-to workaround my problem would be appreciated!
forms.py
class PartForm(forms.ModelForm):
subcategory = forms.ChoiceField(choices=[])
class Meta:
model = PhysicalPart
fields = ['subcategory', 'short_description']
views.py
class PartUpdate(UpdateView):
model = PhysicalPart
template_name = 'part_update.html'
form_class = PartForm
def post(self, request, *args, **kwargs):
# Load model instance
self.object = self.get_object()
# Load form
form = super(PartUpdate, self).get_form(self.form_class)
# Populating subcategory choices
form.fields['subcategory'].choices = SubcategoryFilter[self.object.category]
# Check if form valid and save data
if form.is_valid():
form.save()
return redirect('part-list')
# Update context before rendering
context = self.get_context_data(**kwargs)
context['part_id'] = self.object.pk
context['part_category'] = self.object.category
context['manufacturing_list'] = self.object.manufacturing.all()
return render(request, self.template_name, context)
html
<form action="{% url 'part-update' pk=part_id category=part_category %}" method="post" style="display: inline">
{% csrf_token %}
<div class="form">
<p class="font-weight-bold">Type</br>
{{ form.subcategory }}
</p>
</div>
<div class="form">
<p class="font-weight-bold">Short Description</br>
{{ form.short_description }}
</p>
</div>
<button type="submit" class="btn btn-primary">Save</button>
</form>
<form action="{% url 'part-list' %}" style="display: inline">
<button type="submit" class="btn btn-danger">Cancel</button>
</form>
My problem was that I did not differentiate the "GET" versus the "POST" calls in the UpdateView class, I was trying to do everything in the post() method. It took me a while to figure it out but now I think it's clear.
I originally used the get() method but I realize that get_context_data() was better suited as it automatically loads most of the context (eg. the instance and the form), instead of having to do everything from scratch in the get() method.
Scrubbing through the code of the UpdateView class here, it also seemed necessary to add ModelFormMixin into the declaration of the PartUpdate class so that the get_context_data() method automatically loads the form associated to the target model/instance (else it looks like it won't do it).
Here is my updated views.py code:
class PartUpdate(UpdateView, ModelFormMixin):
model = PhysicalPart
template_name = 'part_update.html'
form_class = PartForm
success_url = reverse_lazy('part-list')
def get_context_data(self, **kwargs):
# Load context from GET request
context = super(PartUpdate, self).get_context_data(**kwargs)
# Get id from PhysicalPart instance
context['part_id'] = self.object.id
# Get category from PhysicalPart instance
context['part_category'] = self.object.category
# Add choices to form 'subcategory' field
context['form'].fields['subcategory'].choices = SubcategoryFilter[self.object.category]
# Return context to be used in form view
return context
def post(self, request, *args, **kwargs):
# Get instance of PhysicalPart
self.object = self.get_object()
# Load form
form = self.get_form()
# Add choices to form 'subcategory' field
form.fields['subcategory'].choices = SubcategoryFilter[self.object.category]
# Check if form is valid and save PhysicalPart instance
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
From my understanding you are trying to edit an instance. This is how you do it in Django, it should autopopulate your inputs with the proper values :
my_record = MyModel.objects.get(id=XXX)
form = MyModelForm(instance=my_record)
More details on this answer : how to edit model data using django forms
If your models are properly done (with relations) you shouldn't need to provide the choices for the Select.
I want to make a comments section in my post detail page. For that i was watching a tutorial for that on youtube. Here the tutorial uses function based view and i want to make it class based view.
Can anyone please help me convert this to class based view
in function based view
def post_detail(request, slug=None):
instance = get_object_or_404(Post, slug=None)
content_type = ContentType.objects.get_for_model(Post)
obj_id = Post.id
comments = Comment.objects.filter(content_type=content_type, object_id=obj_id)
context = {
"title": instance.title,
"instance": instance,
"comments": comments,
}
return render(request, "post_detail.html", context)
so far i tried this way to make it class based which i know is wrong.
class PostDetailView(LoginRequiredMixin,DetailView):
model = Post
template_name = 'posts/post_detail.html'
content_type = ContentType.objects.get_for_model(Post)
obj_id = Post.id
comments = Comment.objects.filter(content_type=content_type, object_id=obj_id)
But this gives me error something like this
return int(value) TypeError: int() argument must be a string, a bytes-like object or a number, not 'DeferredAttribute'
There are issues in your function view as well. Looking at your class based view, if you want to display comments in your detail view, then you don't want any of those thing, all you need this,
class PostDetailView(DetailView):
model = Post
template_name = 'posts/post_detail.html'
To show comments related to the post all you need to use your related name, in your comment model you should name something like this,
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='postcomments')
# ... (other code)
To show this in html, all you need to do this,
{% for comment in post.postcomments.all %}
{{comment.text}} #add according to your model
{% endfor %}
To create comment in the same page you need to add some extra things, Make sure you have comment form as well.
class PostDetailView(DetailView):
model = Post
template_name = 'post/post_detail.html'
def get_context_data(self, **kwargs):
context = super(PostDetailView, self).get_context_data(**kwargs)
context['commentform'] = CommentForm()
return context
def post(self, request, pk):
post = get_object_or_404(Post, pk=pk) #Assuming you have <int:pk> in url, if you have slug change pk to slug
form = CommentForm(request.POST) #Add your comment form
if form.is_valid():
obj = form.save(commit=False)
obj.post = post
obj.user = self.request.user
obj.save()
return redirect('detail', post.pk) # Correct it according your urlpattern name
In your same post detail html you can simply add your same html as you have used in other form templates,
<form method="POST" action="" >
{% csrf_token %}
<p> {{form}} </p>
<button type="submit"> Create </button>
</form>
I have a little problem I've create a edit form to update the existing records. The form is displaying correctly, but when I click the edit button to update the records, the url redirecting me and record is not updated.
My views.py resposible for edit:
#login_required
def szczegoly_pracownik(request, id):
link_pracownik = get_object_or_404(Cudzoziemiec, id=id)
return render(request, 'cudzoziemiec/szczegoly_pracownik.html', {'link_pracownik': link_pracownik})
#login_required
def edycja_pracownika(request, id):
link_pracownik = get_object_or_404(Cudzoziemiec, id=id)
if request.method == 'POST':
edycja_pracownika = CudzoziemiecForm(request.POST, instance=link_pracownik)
if edycja_pracownika.is_valid():
link_pracownik = edycja_pracownika.save(commit=False)
link_pracownik.save()
return render('szczegoly_pracownik', id=link_pracownik.id)
else:
edycja_pracownika = CudzoziemiecForm(request.user, instance=link_pracownik)
return render(request, 'cudzoziemiec/edycja_pracownika.html', {'edycja_pracownika': edycja_pracownika})
The def szczegoly_pracownika is responsible for displaying the detail view
File edycja_pracownika.html
{% if request.user.is_authenticated %}
<form action="." method="post">
{{ edycja_pracownika.as_p }}
{% csrf_token %}
<div class="float-right">
<p><input type="submit" value="Edytuj" ></p>
{% endif %}
and the urls.py responsible for detail view and edit view
...
path('pracownik/<id>', edycja_pracownika, name='edycja_pracownika'),
path('pracownik/<id>/', szczegoly_pracownik, name='szczegoly_pracownik'),
Maybe somebody know where is the bug?
EDIT:
forms.py
class CudzoziemiecForm(forms.ModelForm):
class Meta:
model = Cudzoziemiec
fields = ('nazwa','imie', 'nazwisko','obywatelstwo', 'data_ur','miejsce_ur','paszport','biometria', 'data_start_pasz', 'data_koniec_pasz', 'dok_pobytowy','cel_wizy', 'data_start_pobyt', 'data_koniec_pobyt')
def __init__(self, user, *args, **kwargs):
super(CudzoziemiecForm, self).__init__(*args, **kwargs)
self.fields['nazwa'].queryset = user.firma_set.all()
self.user = user
def save(self, commit=True):
instance = super(CudzoziemiecForm, self).save(commit=False)
instance.user = self.user
if commit:
instance.save()
return instance
I don't arrive to see the real problem but a couple of things that I would try.
You are using the same almost the same url for different pages. It should not be a problem, but I see that as a possible pitfall. Why don't use something like r'^pracownik/edytuj/$' for the editing form? (sorry for my attempt to make a Polish url :-) ).
Maybe that can avoid possible problems and help to clarify the error.
Also when you say that:
the url redirecting me
Do you mean you are redirected to the form again or to the detail page?
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