I am submitting a POST request via django form to my Django Rest Framework api.
Here is a snippet of my form:
<form action="{% url 'entry-list' %}" method="POST" class="form" role="form">
{% csrf_token %}
{{form.as_p}}
<div class = "form-group">
<button type="submit" class="save btn btn-default btn-block">Save</button>
</div>
views.py:
class entry_ViewSet(viewsets.ModelViewSet):
queryset = Entry.objects.all()
serializer_class= EntrySerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,IsOwnerOrReadOnly,)
def perform_create(self, serializer):
serializer.partial = True
serializer.save(created_by=self.request.user)
I am making a successful POST (and item is created in database), however once I save I go to the url /api/entry/ which shows my api w/Markdown. I'd like to have it go back to a specific url.
Is there a way to customize where the POST redirect to if successful?
Added Serializer:
class EntrySerializer(serializers.ModelSerializer):
created_by = serializers.ReadOnlyField(source='created_by.username')
class Meta:
model = Entry
fields = '__all__'
def __init__(self, *args, **kwargs):
super(EntrySerializer, self).__init__(*args, **kwargs)
for x in self.fields:
self.fields[x].required = False
viewsets.ModelViewSet has a method create that returns Response object. The response object is subtype of Django Response. Hence you can change behavior using HttpResponseRedirect into the create method. For example:
class entry_ViewSet(viewsets.ModelViewSet):
queryset = Entry.objects.all()
serializer_class= EntrySerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,IsOwnerOrReadOnly,)
def create(self, request, *args, **kwargs):
response = super(entry_ViewSet, self).create(request, *args, **kwargs)
# here may be placed additional operations for
# extracting id of the object and using reverse()
return HttpResponseRedirect(redirect_to='https://google.com')
def perform_create(self, serializer):
serializer.partial = True
serializer.save(created_by=self.request.user)
Related
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.
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.
I am trying to create a form to submit a blog post on an author detail page, so that the blog post will automatically use the current author as its "blog_author" foreign key. I'm aware that this approach isn't "secure" - it's a project site, and I'm trying to learn a new design pattern.
The Django docs recommended using 1 parent view and 2 subviews to handle get and post respectively (https://docs.djangoproject.com/en/3.0/topics/class-based-views/mixins/).
The page renders fine with the get, but the post gives me an error reading "Page not found (404) - no blog post found matching the query." The exception is raised by my parent view (blog.views.AuthorDetail), but there is no traceback.
Edit: Form should have been a ModelForm from the beginning
Here are my views:
class BlogAuthorDetailView(generic.DetailView):
model = BlogAuthor
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = BlogSubmitForm()
return context
class BlogSubmit(SingleObjectMixin, FormView):
template_name = 'blogauthor_detail.html'
form_class = BlogSubmitForm
model = BlogPost
def post(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return HttpResponseForbidden()
self.object = self.get_object()
#Should I be overriding form_valid() to use the line above? Not sure if I'm doing my data
#handling in the right place
return super().post(request, *args, **kwargs)
def form_valid(self, form):
blogpost = form.save(commit=False)
blogpost.blog_author = self.object
blogpost.save()
return redirect('blog_author-detail', pk=self.object.id)
class AuthorDetail(View):
def get(self, request, *args, **kwargs):
view = BlogAuthorDetailView.as_view()
return view(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
view = BlogSubmit.as_view()
return view(request, *args, **kwargs)
URLs:
urlpatterns = [
path('', views.index, name='index'),
path('blogs/', views.BlogPostListView.as_view(), name='blogs'),
path('blog/<int:pk>', views.BlogPostDetailView.as_view(), name='blogpost-detail'),
path('bloggers/', views.BlogAuthorListView.as_view(), name='bloggers'),
path('blogger/<int:pk>', views.AuthorDetail.as_view(), name='blog_author-detail'),
path('blog/<int:pk>/create', views.BlogCommentCreate.as_view(), name='comment_create')
]
the template:
{% extends "base_generic.html" %}
{% block content %}
<h1>Title: {{ blogauthor.title }}</h1>
<p><strong>Author:</strong> {{ blogauthor }}</p>
<p><strong>Biography:</strong> {{ blogauthor.biography }}</p>
<p><strong>User:</strong> {{ blogauthor.user }}</p>
<p><strong>Posts:</strong>
{% for blog in blogauthor.blogpost_set.all %}
<p> {{ blog.title }} </p>
{% endfor %} </p>
<form action="" method="post">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="Submit">
</form>
<div style="margin-left:20px;margin-top:20px">
<h4>Comments: Coming Soon!</h4>
{% endblock %}
Model:
class BlogPost(models.Model):
date_created = models.DateField(blank=False, default = date.today)
blog_author = models.ForeignKey('BlogAuthor', on_delete = models.SET_NULL, null=True)
title = models.TextField(max_length=70)
content = models.TextField(max_length=400, null=False)
class Meta:
ordering = ['date_created']
def get_absolute_url(self):
"""Returns the url to access a particular blog post instance."""
return reverse('blogpost-detail', args=[str(self.id)])
def __str__(self):
return self.title
And the forms.py:
class BlogSubmitForm(forms.Form):
title = forms.CharField()
content = forms.CharField(widget=forms.Textarea(attrs={'cols': 40, 'rows': 8}))
date_created = forms.DateField()
At this point, I suspect that the problem is related to my redirect() call in the form_valid override.
The things I have tried include:
Changing the form’s action from blank to the same URL as in my URL paths (possible I did this wrong)
Changing the code in form_valid() to read form.instance.blog_author = self.object (same exact error message, so I don’t think it’s this)
Fiddling with the form_valid()’s redirect call, including: using self.object instead or a URL, using a hardcoded url, getting rid of the second argument, and changing the 2nd arg to pk=, slug=.
Adding a get_success_url override (don’t really know why this would work)
edit: one of the excepted post calls that showed up in my local server went to blog/blogger/4, which is the url I want. Not sure what the issue is.
This is confusing on how you are using the template. Anyway, I think the simplest solution here is to get the BlogAuthor data from request.user and that is most logical, otherwise, anyone can post anything from another user as long as they can predict their primary key(which is a security hole). Here is how you can try:
from django.contrib.auth.mixins import LoginRequiredMixin
class BlogSubmit(LoginRequiredMixin, CreateView):
template_name = 'blogauthor_detail.html'
form_class = BlogSubmitForm
model = BlogPost
def get_success_url(self):
return reverse('blog_author-detail', pk=self.object.id)
def form_valid(self, form):
form.blog_author = self.request.user.blogauthor # assuming BlogAuthor has OneToOne relation with User
return super(BlogSubmit, self).form_valid(form)
Update
Purpose of FormView is to collect data from Forms, where CreateView is to store and create a new instance. Anyway, you need to change your code like this to make it work:
class BlogSubmit(LoginRequiredMixin, SingleObjectMixin, FormView):
template_name = 'blogauthor_detail.html'
form_class = BlogSubmitForm
model = BlogAuthor
def get_success_url(self):
return reverse('blog_author-detail', pk=self.object.id)
def form_valid(self, form):
self.object = self.get_object()
form.blog_author = self.object
form.save()
return super(BlogSubmit, self).form_valid(form)
Also update the form:
class BlogSubmitForm(forms.ModelForm):
class Meta:
model = BlogPost
fields = ['title', 'date_created', 'content']
FYI, to make SingleObjectMixin work, you need to change the model from BlogPost to BlogAuthor
I'm super new to django. I am trying to create a clone of pastebin.com which has only one model (Post) with name , content and generated_url.
I am having problem with the searchbar . I dont know how to implement the SearchView into search.html that generate
here's my model
from django.db import models
from django.urls import reverse
# Create your models here.
class Post(models.Model):
name = models.CharField(db_index=True, max_length=300, blank=False)
content = models.TextField()
generated_url = models.CharField(db_index=True, max_length=10, blank=False)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse("pastebin_app:detail",kwargs={'pk':self.pk})
here's my root.html for the searchbar
<form action="{% url 'pastebin_app:search' %}" method="get" accept-charset="utf-8">
<input name="q" type="text" placeholder="Search">
<input type="submit" value="Search"/>
</form>
and here's the views.py for searchview
class SearchView(ListView):
template_name = 'pastebin_app/search.html'
model = models.Post
def get(self, request, *args, **kwargs):
q = request.GET.get('q', '')
self.results = models.Post.objects.filter(name__icontains=q)
return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
return super().get_context_data(results=self.results, **kwargs)
Can someone please help me creating the show.html template that produce the search result from the SearchView?
You need to checkout django-filter contains a simple api to help you get setup with filtering objects and also how to handle complex filters.
def product_list(request):
f = ProductFilter(request.GET, queryset=Product.objects.all())
return render(request, 'my_app/template.html', {'filter': f})
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)