For my classes PostCreateView and PostUpdateView I have the same get_success_url. Is there a way of standardising your get_success_url, I want to avoid writing the code twice?
def get_success_url(self):
return reverse('post-detail', args=[self.object.pk])
I was working through a tutorial. A limitation of the tutorial though was you were only shown how to load posts by using the posts primary key (https://rossdjangoawesomeapp2.herokuapp.com/post/6/). I modified the code in the tutorial so you could access posts using their title (https://rossdjangoawesomeapp2.herokuapp.com/post/another-test-please-let-it-work/). Which has resulted in me having duplicate code.
models.py
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
date_posted = models.DateTimeField(default=timezone.now())
author = models.ForeignKey(User, on_delete=models.CASCADE)
url = models.SlugField(max_length=500, blank=True)
def save(self, *args, **kwargs):
self.url= slugify(self.title)
super().save(*args, **kwargs)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk': self.pk})
views.py
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'content']
def get_success_url(self):
return reverse('post-detail', args=[self.object.pk])
def form_valid(self, form):
form.instance.author = self.request.user
# will save the form and redirect to the success_url
return super().form_valid(form)
class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Post
fields = ['title', 'content']
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
def test_func(self):
post = self.get_object()
if self.request.user == post.author:
return True
return False
def get_success_url(self):
return reverse('post-detail', args=[self.object.pk])
For my classes PostCreateView and PostUpdateView I have the same get_success_url. Is there a way of standardising your get_success_url, I want to avoid writing the code twice?
Yes, not writing it at all. If you do not specify a get_success_url, or success_url, Django will take the get_absolute_url of the object (so here the Post object). Hence if you do not specify it, then it will already redirect to the correct url, this is specified in the documentation for the get_success_url method [Django-doc]:
Determine the URL to redirect to when the form is successfully validated. Returns django.views.generic.edit.ModelFormMixin.success_url if it is provided; otherwise, attempts to use the get_absolute_url() of the object.
So we can remove the get_success_url methods:
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'content']
# no get_success_url
def form_valid(self, form):
form.instance.author = self.request.user
class PostUpdateView(LoginRequiredMixin, UpdateView):
model = Post
fields = ['title', 'content']
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).filter(
author=self.request.user
)
# no get_success_url
Related
This error only happens if multiple users have the same title for their posts.
For example, if john has a page with a title 'mypage' this is the error shown if another user has the same title for their page as john.
`MultipleObjectsReturned at /john/mypage/update/` get() returned more than one Post -- it returned 2!
but if no one else has it, no error is shown when trying to update the post.
class PostUpdateView(LoginRequiredMixin, UpdateView):
model = Post
form_class = PostForm
def form_valid(self, form):
form.instance.author = self.request.user ##author = current logged in user
return super().form_valid(form)
def test_func(self):
post = self.get_object()
if self.request.user == post.author:
return True
return False
class PostDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Post
success_url = '/'
def test_func(self):
post = self.get_object()
if self.request.user == post.author:
return True
return False
Model
class Post(models.Model):
title = models.CharField(max_length=100, default=None)
author = models.ForeignKey(User, on_delete=models.CASCADE)
slug = AutoSlugField(populate_from='title', null=True)
def get_absolute_url(self):
return reverse('post-detail', kwargs={'slug': self.slug, 'author': self.author})
urls
urlpatterns = [
path('landingpage/new/', PostCreateView.as_view(), name='post-create'),
path('<str:author>/<slug:slug>/', PostDetailView.as_view(), name='post-detail'),
path('<str:author>/<slug:slug>/update/', PostUpdateView.as_view(), name='post-update'),
path('<str:author>/<slug:slug>/delete/', PostDeleteView.as_view(), name='post-delete'),
] + static(settings.M
EDIA_URL, document_root=settings.MEDIA_ROOT)
It means that there are two Posts with as slug mypage. Note that you do not filter on the author of your Post, so if two author's have a mypage, then this will raise an error.
You can filter on the author slug with:
class PostUpdateView(LoginRequiredMixin, UpdateView):
model = Post
form_class = PostForm
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).filter(
author__username=self.kwargs['author']
)
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
I am trying to add inlineformset to the Django Blog project. I got post and I want to add multiple images to the post via Class Based Views. But when I am trying to get_context_data I receive this error:
get_context_data() missing 1 required positional argument: 'form'
Models.py:
class Post(models.Model):
title = models.CharField(max_length=100)
content = RichTextField(blank=True, null=True)
class PostImage(models.Model):
post = models.ForeignKey(Post, default=None, on_delete=models.CASCADE)
image = models.ImageField(upload_to='gallery/')
description = models.CharField(max_length=300, blank=True)
Forms.py
class CreatePostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title' , 'content' ]
PostImageFormSet = inlineformset_factory(Post, PostImage, fields=['image', 'description'], exclude=['post'] , max_num=30, extra=3)
Views.py
class PostCreateView(LoginRequiredMixin, CreateView):
form_class = CreatePostForm
template_name = 'blog/post_form.html'
def get_context_data(self, form):
context = super(PostCreateView. self).get_context_data(**kwargs)
if self.request.POST:
context['post_images'] = PostImageFormSet(self.request.POST)
else:
context['post_images'] = PostImageFormSet()
return context
def form_valid(self, form):
context = self.get_context_data(form=form)
formset = context['post_images']
form.instance.author = self.request.user
if formset.is_valid():
response = super().form_valid(form)
formset.instance = self.object
formset.save()
return response
else:
return super().form_invalid(form)
Any idea why I am receiving this kind of error? Thank you.
You should remove the form parameter. The form is never used in the get_context_data anyway:
class PostCreateView(LoginRequiredMixin, CreateView):
form_class = CreatePostForm
template_name = 'blog/post_form.html'
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
if self.request.POST:
context['post_images'] = PostImageFormSet(self.request.POST)
else:
context['post_images'] = PostImageFormSet()
return context
def form_valid(self, form):
context = self.get_context_data()
formset = context['post_images']
form.instance.author = self.request.user
if formset.is_valid():
response = super().form_valid(form)
formset.instance = self.object
formset.save()
return response
else:
return super().form_invalid(form)
I hope you are well.
I'm trying to create comment for user with one form field (content). I'd like to have the user field automatically filled in user value.
I. I started with this model but I got an error (1048, “Column 'user_id' cannot be null”):
models.py
class Comment(models.Model):
post = models.ForeignKey(Post,on_delete=models.CASCADE,related_name="comments")
user = models.ForeignKey(User,on_delete=models.CASCADE)
content = models.TextField(max_length=160)
publishing_date = models.DateField(auto_now_add=True)
def __str__(self):
return self.post.title
views.py
class PostDetail(generic.DetailView,FormMixin):
model = Post
context_object_name = 'post'
template_name = 'post_detail.html'
form_class = CreateCommentForm
def get_context_data(self, **kwargs):
context = super(PostDetail, self).get_context_data(**kwargs)
context['form'] = self.get_form()
return context
def form_valid(self, form):
if form.is_valid():
form.instance.post = self.object
form.save()
return super(PostDetail, self).form_valid(form)
else:
return super(PostDetail, self).form_invalid(form)
def post(self,*args,**kwargs):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_valid(form)
def get_success_url(self):
return reverse('post_detail',kwargs={"slug":self.object.slug})
forms.py
class CreateCommentForm(forms.ModelForm):
def __init__(self,*args,**kwargs):
super(CreateCommentForm, self).__init__(*args,**kwargs)
self.helper = FormHelper()
self.helper.form_method="post"
self.helper.layout = Layout(
Field("content",css_class="form-control",style="margin-bottom:10px",rows="1"),
)
self.helper.add_input(Submit('submit','Comment',css_class="btn btn-sm",style="background-color: #0d6ec5;border-color: #0d6ec5;"))
class Meta:
model = Comment
fields = [
'content'
]
II. It worked from my admin panel but not from my website (I got this error: (1048, “Column 'user_id' cannot be null”)).
So I've decided to change my models with:
class Comment(models.Model):
post = models.ForeignKey(Post,on_delete=models.CASCADE,related_name="comments")
user = models.ForeignKey(User, blank=True, null=True, on_delete=models.CASCADE)
content = models.TextField(max_length=160)
publishing_date = models.DateField(auto_now_add=True)
def __str__(self):
return self.post.title
The thing is if a user post a comment, content is ok but there is no user related to the comment. Anyone has an idea?
You need to specify the .user attribute, probably to the logged in user:
from django.contrib.auth.mixins import LoginRequiredMixin
class PostDetail(LoginRequiredMixin, FormMixin, generic.DetailView):
model = Post
context_object_name = 'post'
template_name = 'post_detail.html'
form_class = CreateCommentForm
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = self.get_form()
return context
def form_valid(self, form):
form.instance.post = self.object
form.instance.user = self.request.user
return super().form_valid(form)
def post(self,*args,**kwargs):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_valid(form)
def get_success_url(self):
return reverse('post_detail',kwargs={"slug":self.object.slug})
Note: You can limit views to a class-based view to authenticated users with the
LoginRequiredMixin mixin [Django-doc].
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Im trying to pass the "test" argument from this View to a Form:
class ListCreateFormView(CreateView):
template_name = 'armybuilder_list_create.html'
model = List
form_class = CreateListForm
def form_valid(self, form):
form.instance.author = self.request.user
self.test = Faction.objects.get(title="Blood Angels")
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy('add_unit_to_list', kwargs={'pk': self.object.pk, 'test': self.test.id})
To this View and Form:
class AddUnitsToListFormView(LoginRequiredMixin,CreateView):
model = SoldierToList
form_class = SoldierToListForm
template_name = 'armybuilder_add_unit_to_list.html'
def form_valid(self, form):
form.instance.author = self.request.user
form.instance.list_id = self.kwargs['pk']
return super().form_valid(form)
class SoldierToListForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# factions_obj = Faction.objects.get(pk=my_test)
self.fields['soldier'].queryset = Soldier.objects.filter(factions=4)
class Meta:
model = SoldierToList
fields = ('soldier',)
My urls:
urlpatterns = [
path('list/create', ListCreateFormView.as_view(), name='create_list'),
path('list/own/<int:pk>/<int:test>/addunit', AddUnitsToListFormView.as_view(), name='add_unit_to_list'),
]
Im hardcoding the value 4 in my form but I would like to use the argument test that I defined in the previous View ListCreateFormView, can you please tell me how to do it ?
Thanks
I keep getting this error "ValueError at /post/new/
Cannot assign ">": "Post.author" must be a "User" instance."
class User(models.Model):
user = models.OneToOneField(User,on_delete = models.CASCADE)
def __str__(self):
return self.user.username
class Post(models.Model):
author = models.ForeignKey('User.User',related_name="posts",on_delete = models.CASCADE)
text = models.TextField()
created_at = models.DateTimeField(default = timezone.now)
updated_at = models.DateTimeField(blank = True,null =True)
def update(self):
updated_at = timezone.now()
self.save()
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk': self.pk})
views.py
class CreatePostView(LoginRequiredMixin, CreateView):
# form_class = forms.PostForm
fields = ['text',]
model = Post
def form_valid(self, form):
self.object = form.save(commit=False)
form.instance.author = self.request.user
self.object.save()
return super().form_valid(form)
Please help!
You here made two User objects, the User object from django.contrib.auth and one from the User app. In order to retrieve the related User object from the User app, you can use self.request.user.user (given you of course constructed that related object):
class CreatePostView(LoginRequiredMixin, CreateView):
# form_class = forms.PostForm
fields = ['text',]
model = Post
def form_valid(self, form):
self.object = form.save(commit=False)
form.instance.author = self.request.user.user
self.object.save()
return super().form_valid(form)
It is however very confusing to name two models the same. It might be better to just use Profile for example. In that case it would be form.instance.author = self.request.user.profile which is also more readable.