how to have multiple forms in a class based view - django

I have a simple blog app that you can post news and blog on it. I wanted to show the latest posts on the main page, so I created this class like this:
class MainListView(TemplateView):
template_name = 'Blog/Home.html'
def get_context_data(self, **kwargs):
context = super(MainListView, self).get_context_data(**kwargs)
context['news'] = NEWS.objects.order_by('-published')[:2]
context['posts'] = POSTS.objects.order_by('-published')[:3]
return context
and it works great. however, I wanted to add two additional forms to the main page too. so I added these two functions to the class:
def get(self, request, *args, **kwargs):
return self.render_to_response({'aform': HomeGholakForm(prefix='aform_pre'), 'bform':
HomeContactForm(prefix='bform_pre')})
def post(self, request, *args, **kwargs):
aform = _get_form(request, HomeGholakForm, 'aform_pre')
bform = _get_form(request, HomeContactForm, 'bform_pre')
if aform.is_bound and aform.is_valid():
aform.save()
return redirect('Home-Page')
elif bform.is_bound and bform.is_valid():
bform.save()
return redirect('Home-Page')
return self.render_to_response({'aform': aform, 'bform': bform})
and above this class I created a _get_form function in order for it to work:
def _get_form(request, formcls, prefix):
data = request.POST if prefix in request.POST else None
return formcls(data, prefix=prefix)
in the final stage I added the forms to my template like this:
<form action="">
{% csrf_token %}
{{bform.email}}
<input type="submit" name="{{bform.prefix}}" class="btn" />
</form>
<form action="">
{% csrf_token %}
{{aform.info}}
<input type="submit" name="{{aform.prefix}}" class="btn" />
</form>
After I did this, the forms both work fine, but the latest blog and news are not shown. what am I supposed to do?

get_context_data is normally called by the get or post method whichever is used (only get in TemplateView) and passed as the context while rendering. You override these methods but never call get_context_data. Change your get and post methods like so:
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
context.update({'aform': HomeGholakForm(prefix='aform_pre'), 'bform': HomeContactForm(prefix='bform_pre')})
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
aform = _get_form(request, HomeGholakForm, 'aform_pre')
bform = _get_form(request, HomeContactForm, 'bform_pre')
if aform.is_bound and aform.is_valid():
aform.save()
return redirect('Home-Page')
elif bform.is_bound and bform.is_valid():
bform.save()
return redirect('Home-Page')
context = self.get_context_data(**kwargs)
context.update({'aform': aform, 'bform': bform})
return self.render_to_response(context)
You should consider making some Mixin along the lines of FormMixin which would make your work more easier.

Related

Combining DetailView and CreateView in Django 3.2/4.0, as explained in the docs

I'm building a generic blog and trying to enable users to make comments directly on the article page. I am trying to implement this by combining DetailView with CreateView.
The docs present 3 different solutions to this issue:
FormMixin + DetailView: this is the answer that Django docs advise against, but that is advised by most answers on SO that I could find
DetailView only + write the post method: "a better solution" according to Django docs
DetailView + FormView: "an alternative better solution", and the one I'm trying to implement.
The "alternative better" solution consists in making a DetailView for articles and a FormView for comments, but the docs state that "This approach can also be used with any other generic class-based views", which means that DetailView + CreateView should be possible.
I've gone through a number of SO items that reference this solution, but I am unable to implement any of them.
This SO question suggests mixing DetailView and CreateView. However, the explanation in that answer is incomplete.
Another SO question, among advice to use FormMixins, has this answer that is close, but different.
Other questions (1, 2, etc.) only address the FormMixin and DetailView + post methods.
Here's my implementation for now:
models.py:
class Article(models.Model):
slug = models.SlugField()
# title, body, author
def get_absolute_url(self):
return reverse("article_detail", kwargs={"slug": self.slug})
class Comment(models.Model):
article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name="comments", to_field="slug")
body = models.TextField()
# ...
def get_absolute_url(self):
return reverse("article_detail", kwargs={"slug": self.article.slug})
views.py:
class ArticleDetailView(DetailView):
model = Article
template_name = "article_detail.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["form"] = CommentCreateView()
return context
class CommentCreateView(CreateView):
"""create comment"""
model = Comment
fields = ["body"]
template_name = "article_detail.html"
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.article = Article.objects.filter(
slug=self.kwargs.get("slug")
).first()
self.object.author = self.request.user
self.object.save()
return super().form_valid(form)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
return super().post(request, *args, **kwargs)
def get_success_url(self):
return reverse("article_detail", kwargs={"slug": self.object.article.slug})
class ArticleCommentView(View):
def get(self, request, *args, **kwargs):
view = ArticleDetailView.as_view()
return view(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
view = CommentCreateView.as_view()
return view(request, *args, **kwargs)
urls.py:
urlpatterns = [
# ...
path("article/<slug:slug>", ArticleCommentView.as_view(), name="article_detail"),
]
article_detail.html:
{% extends 'base.html' %}
{% block content %}
{{ article.title }}
{{ article.author }}
{{ article.body }}
{% include "comment_create.html" %}
<!-- list existing comments with {#% for comment in article.comments.all %#}, etc.-->
{% endblock %}
comment_create.html:
<form method="post" action="{% url 'article_detail' slug=article.slug %}">
{% csrf_token %}
<textarea name="{{ form.body.name }}">{{ form.body.value|default_if_none:'' }}</textarea>
</div>
<button type="submit">
Post Comment
</button>
</div>
</form>
I'm currently getting a
NoReverseMatch at /article/createview-cd7a7040-c4ca-4289-8f53-6676f27c3aa9
Reverse for 'editor_update' with keyword arguments '{'slug': ''}' not found. 1 pattern(s) tried: ['editor/(?P<slug>[-a-zA-Z0-9_]+)$']`
where editor_update is a path at editor/<slug> to an UpdateView. Can't understand how that is related to anything.
The article.slug in comment_create.html returns the correct slug, but form.body.name returns an empty string, if that helps.
Edited based on Abdul Aziz Barkat's comment below.

Django Context isnt rendered

I want to render a view with some content. I don't get why the context isn't rendered.
The render_recall_details() function causes problems --> ctx is not None, In my opinion there is no reason why it is not rendered in the html file
views.py
class RecallDetail(View):
template_name = "recall_detail.html"
def get(self, request, *args, **kwargs):
if request.GET:
q = request.GET
q = q.dict()
recall = find_recall_by_querystring(q)
if recall:
self.render_recall_details(request, recall)
else:
return render(request, self.template_name)
return render(request, self.template_name)
def render_recall_details(self, request, obj, *args, **kwargs):
ctx = {
'head': 'Hallo',
'rec': RecallForm(),
'docs': find_docs(obj),
}
print(ctx)
return render(request, self.template_name, context=ctx)
forms.py
class RecallForm(forms.ModelForm):
class Meta:
model = Recall
fields = ('Recall_CODE', 'Recall_NAME', 'Recall_DESCRIPTION', 'Recall_START_DATE', 'Recall_PLANNED_COMPLETATION_DATE', 'Recall_STATUS', 'Recall_DATE_COMPLETED')
my html_template:
{% extends 'base.html' %}
{% block content %}
<div class="content-wrapper">
<h1>{{ head }}</h1>
...
</div>
{% endblock content %}
It seems like a typo in your code-base. In Django, every view must return a response. So, in your case, you are not returning anything if the variable recall has some value. So, change your view as,
class RecallDetail(View):
template_name = "recall_detail.html"
def get(self, request, *args, **kwargs):
if request.GET:
q = request.GET
q = q.dict()
recall = find_recall_by_querystring(q)
if recall:
# you need to put the `return` statement here
return self.render_recall_details(request, recall)
else:
return render(request, self.template_name)
return render(request, self.template_name)
def render_recall_details(self, request, obj, *args, **kwargs):
ctx = {
'head': 'Hallo',
'rec': RecallForm(),
'docs': find_docs(obj),
}
print(ctx)
return render(request, self.template_name, context=ctx)

Django ModelForm not saving data even though form.save is executed

I have a website where user have 2 model for their profile, user_detail and user_location. I tried to serve 2 model form on one page with one submit. The problem is when the data from those model form does not save in to the database.
I confirmed that self.request.POST in the post method returns the correct data.
I tried :
Django ModelForm not saving data to database - Does not work
Django ModelForm not saving data - Does not work
The following code if for admins.
Here is my view :
class UpdateProfile(LoginRequiredMixin, UpdateView):
template_name = 'account/user_profile.html'
fields = '__all__'
model = models.UserProfile
user_detail_form_class = forms.UserDetailForm
user_location_form_class = forms.UserLocationForm
def get_context_data(self, **kwargs):
user_profile = get_object_or_404(models.UserProfile, pk=self.kwargs.get(self.pk_url_kwarg))
context = super(UpdateProfile, self).get_context_data(**kwargs)
if 'user_detail_form' not in context:
context['user_detail_form'] = self.user_detail_form_class(instance=user_profile.user_detail)
if 'user_location_form' not in context:
context['user_location_form'] = self.user_location_form_class(instance=user_profile.user_location)
return context
def get(self, request, *args, **kwargs):
super(UpdateProfile, self).get(request, *args, **kwargs)
return self.render_to_response(self.get_context_data())
def post(self, request, *args, **kwargs):
user_detail_form = self.user_detail_form_class(request.POST)
user_location_form = self.user_location_form_class(request.POST)
if user_detail_form.is_valid() and user_location_form.is_valid():
user_detail_form.save()
user_location_form.save()
return redirect(self.get_success_url())
else:
return self.render_to_response(self.get_context_data())
def get_success_url(self):
return reverse('account:admin_client_list')
def dispatch(self, request, *args, **kwargs):
if not request.user.groups.filter(name__in=['Admin']).exists():
return errors.render_403(request)
return super(UpdateProfile, self).dispatch(request, *args, **kwargs)
Here is my template :
{% extends 'base.html' %}
{% block content %}
<form method='POST' action="">{% csrf_token %}
{{ user_detail_form }}
{{ user_location_form }}
<input type="submit" value="Submit">
</form>
{% endblock %}
Here is the form :
class UserDetailForm(forms.ModelForm):
class Meta:
model = models.UserDetail
fields = '__all__'
class UserLocationForm(forms.ModelForm):
class Meta:
model = models.UserLocation
fields = '__all__'
You need to pass the instance parameter when you are creating the ModelForm in the post method. Sample code:
user_profile = get_object_or_404(models.UserProfile, pk=self.kwargs.get(self.pk_url_kwarg))
user_detail_form = self.user_detail_form_class(request.POST, instance=user_profile.user_detail)
user_location_form = self.user_location_form_class(request.POST, instance=user_profile.user_location)

FormView with get method doesn't show the form

Thanks for read :/
When I remove the "get" from the view, the form works, but when I put it back, it doesn't render.
URL at the browser:
http://127.0.0.1:8000/activate?tpr=1140277&idpr=42
URLs:
url(r'^activate', Activation_vw.as_view(), name='activate')
VIEW:
class Activation_vw(FormView):
template_name = 'activate.html'
form_class = Activation_Form
success_url = '/dashboard/'
def get(self, request, *args, **kwargs):
tokenProspect_v = request.GET.get('tpr')
idProspect_v = request.GET.get('idpr')
USER = USERS.objects.filter(
id=idProspect_v).values('id', 'email', 'token')
if int(tokenProspect_v) != int(USER[0]['token']):
message = "Check the URL"
else:
message = USER[0]['email']
context = {'msg': message}
return self.render_to_response(context)
def form_valid(self, form):
# No code yet
return super(Activation, self).form_valid(form)
FORM:
class Activation_Form(forms.Form):
email = forms.EmailField()
password = forms.CharField(widget=forms.PasswordInput())
TEMPLATE:
Hello, {{ msg }}
<form action="" method="POST">
{%csrf_token%}
{{ form.as_p }}
<button type="submit">Activate</button>
</form>
All of them have the imports at the top of each file.
The get function works perfectly, I receive the tpr and the idpr but the form doesn't and because of that the form form_valid and the success_url doesn't work neither.
I suspect something is wrong in the return of my get, but can't figured out.
It is because your form is not being passed in the context:
context = {'msg': message}
return self.render_to_response(context)
You can use get_context_data to get the context and update it as you go:
context = self.get_context_data(msg=message)
return self.render_to_response(context)
This will call the get_context_data from the super class and include the form to the context along with adding the message.

How to write get method for class bassed on ListView

I try to write get method for my class bassed on ListView, I want to get request from form in template and return model with filter from request. This is part of my code:
class SearchListView(ListView):
context_object_name = 'projects_list'
template_name = 'projects/search.html'
paginate_by = 10
def get(self, request, *args, **kwargs):
do smth??
return self.render_to_response(??)
My form:
<form class="well form-search" action="/search/" method="get">
<input type="text" class="input-medium search-query" name="q">
<button type="submit" class="btn">Search</button>
</form>
Plz, give me some example.
No need to rewrite get method.
class SearchListView(ListView):
context_object_name = 'projects_list'
template_name = 'projects/search.html'
paginate_by = 10
def get_queryset(self):
query = self.request.GET.get('q')
return Model.objects.filter(title=q)
You can use super() like this:
class SearchListView(ListView):
...
def get(self, request, *args, **kwargs):
do smth??
return super(SearchListView, self).get(request, *args, **kwargs)