How to set ForeignKey in CreateView? - python

I have a model:
class Article(models.Model):
text = models.CharField()
author = models.ForeignKey(User)
How do I write class-based view that creates a new model instance and sets author foreign key to request.user?
Update:
Solution moved to separate answer below.

I solved this by overriding form_valid method. Here is verbose style to clarify things:
class CreateArticle(CreateView):
model = Article
def form_valid(self, form):
article = form.save(commit=False)
article.author = self.request.user
#article.save() # This is redundant, see comments.
return super(CreateArticle, self).form_valid(form)
Yet we can make it short (thanks dowjones123), this case is mentioned in docs.:
class CreateArticle(CreateView):
model = Article
def form_valid(self, form):
form.instance.author = self.request.user
return super(CreateArticle, self).form_valid(form)

I just stumbled into this problem and this thread led me in the right direction (thank you!). Based on this Django documentation page, we can avoid calling the form's save() method at all:
class CreateArticle(LoginRequiredMixin, CreateView):
model = Article
def form_valid(self, form):
form.instance.author = self.request.user
return super(CreateArticle, self).form_valid(form)

Berislav's code in views.py doesn't work for me. The form is rendered as expected, with the user value in a hidden input, but the form is not saved (I don't know why). I have tried a slightly different approach, that works for me:
views.py
from django.views.generic import *
from myapp.forms import ArticleForm
from myapp.models import Article
class NewArticleView(CreateView):
model = Article
form_class = ArticleForm
def get_initial(self):
return {
"user": self.request.user
}

You should set up a CreateView using a ModelForm for that model. In the form definition, you set the ForeignKey to have the HiddenInput widget, and then use the get_form method on the view to set the value of your user:
forms.py:
from django import forms
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
widgets = {"user": forms.HiddenInput()}
views.py:
from django.views.generic import *
from myapp.forms import ArticleForm
from myapp.models import Article
class NewArticleView(CreateView):
model = Article
form_class = ArticleForm
def get_form(self, form_class):
initials = {
"user": self.request.user
}
form = form_class(initial=initials)
return form

There are answers that are mainly related to the User model foreign key. However, let's suppose a simple scenario in which there is a model Comment containing a foreign key of the Article model, and you need to have a CreateView for Comment where each comment will have a foreign key of the Article model. In that case, the Article id would probably be in the URL, for example, /article/<article-id>/comment/create/. Here is how you can deal with such a scenario
class CommentCreateView(CreateView):
model = Comment
# template_name, etc
def dispatch(self, request, *args, **kwargs):
self.article = get_object_or_404(Article, pk=self.kwargs['article_id'])
return super(CommentCreateView, self).dispatch(request, *args, **kwargs)
def form_valid(self, form):
form.instance.article= self.article # if the article is not a required field, otherwise you can use the commit=False way
return super(CommentCreateView, self).form_valid(form)

Related

(Django CBV) Need object be attached to a user with CBV

I implemented this functionality with using FBV, but when I'm trying to use CBV, Objects were created with empty user field.
views.py
class BlockCreate(CreateView):
model = TrainingBlock
template_name = 'training_room/create_block.html'
form_class = BlockForm
success_url = reverse_lazy('gym')
def set_user(self, form):
form.instance.user = self.request.user
return super(BlockCreate, self).set_user(form)
models.py
class TrainingBlock(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=30)
duration = models.IntegerField(default=10)
if_ended = models.BooleanField(default=False)
def __str__(self):
return self.name
forms.py
class BlockForm(forms.ModelForm):
class Meta:
model = TrainingBlock
fields = '__all__'
exclude = ['user']
There is no .set_user method in a CreateView, hence the logic will never get invoked. You use .form_valid(…) [Django-doc] instead:
from django.contrib.auth.mixins import LoginRequiredMixin
class BlockCreate(LoginRequiredMixin, CreateView):
model = TrainingBlock
template_name = 'training_room/create_block.html'
form_class = BlockForm
success_url = reverse_lazy('gym')
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
Note: You can limit views to a view to authenticated users with the
#login_required decorator [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.
Note: Since PEP-3135 [pep], you don't need to call super(…) with parameters if the first parameter is the class in which you define the method, and the second is the first parameter (usually self) of the function.

how can i post form object, correctly?

i am practicing CBV , so i thought to check if i can override methodes, well one of biggest problems is that idk how to use data(like data just submitted ), i wrote this code for a DetailView so i could see post and comments under it:
class ArtDetailView(FormView, DetailView):
model = Art
form_class = CommentForm
def get_context_data(self, **kwargs):
context = super(ArtDetailView, self).get_context_data(**kwargs)
context['time'] = timezone.now()
context['form'] = self.get_form()
return context
def form_valid(self, form):
form.instance.writer = self.request.user
form.instance.text = self.post
#form.instance.art = Art.objects.get(id=self.pk)
form.save()
return super().form_valid(form)
def get_success_url(self) -> str:
return reverse('pages:art_detail', args=(self.kwargs['pk'],))
forms.py:
from django import forms
from .models import Art, Comment
class CommentForm(forms.ModelForm):
class Meta():
model = Comment
fields = ['text','art']
but when i post something it is in this shape:screen_shot(2nd comment)
,i think problem is withform.instance.text = self.post but i don't know how to fix it
can you please also explain a little because all i want is to learn.
and i tried to also add art as autofill(i added as comment) but wasn't successful, can you pls check it it too.
You can overide post method to save form as well. Since you only adding form which is always a text and no possible errors in forms unless blank. You code snippet will look like this
# In forms.py
def CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('text', )
# In views.py
class ArtDetailView(FormView, DetailView):
# same as previous but remove form_valid method
def post(self, request, *args, **kwargs):
form = CommentForm(request.POST)
if form.is_valid():
form_instance = form.save(commit=False)
# this will give form instance and then add related field and save again (commit=True by default)
form_instance.writer = request.user
# since it is art detail view, self.get_object() will give art object
form_instance.art = self.get_object()
form_instance.save()
return HttpResponseRedirect(self.get_success_url())
else:
return super().form_invalid(from)
If you go to FormView declaration you will find like this

Django CBV Detailview

Hello Everybody excuse my english....
I am facing a problem with django.
I need to restrict object so only their owners can print it.
Model.py
class Post(models.Model):
title = models.CharField(max_length=50, blank=False)
prenom = models.CharField(max_length=255, blank=False)
user = models.ForeignKey(User, null=False)
View.py
class detailpost(DetailView):
model = Post
template_name = 'detail-post.html'
context_object_name = 'post'
url.py
url(r'detail-post/(?P<pk>[-\d]+)$', views.detailpost.as_view(), name='detailpost'),
This works properly but the problem is that every users can access to the post of another user (http://localhost:8000/detail-post/1). So my question is how can i do some stuff befor rendering the page and see if the post belongs to the current user if yes we print it else we redirect the user to another page.
You can use the LoginRequiredMixin (new in Django 1.9) to make sure that only logged in users can access the view.
Then override the get_queryset method, and filter the queryset so that it only includes posts by the logged-in user.
from django.contrib.auth.mixins import LoginRequiredMixin
class DetailPost(LoginRequiredMixin, DetailView):
model = Post
template_name = 'detail-post.html'
context_object_name = 'post'
def get_queryset(self):
queryset = super(DetailPost, self).get_queryset()
return queryset.filter(owner=self.request.user)
If the user views a post that does not belong to them, they will see a 404 page. If you must redirect the user instead of showing a 404, then you'll have to take a different approach.
Note that I have renamed your class DetailPost (CamelCase is recommended for classes in Django. You'll have to update your urls.py as well.
You can override get() or post() method in your view class.
from django.shortcuts import redirect
class detailpost(DetailView):
model = Post
template_name = 'detail-post.html'
context_object_name = 'post'
def get(self, request, *args, **kwargs):
self.post = Post.objects.get(pk=self.kwargs['pk'])
if self.post.user != request.user or not request.user.is_superuser:
return redirect('login')
else:
return super(detailpost, self).get(request, *args, **kwargs)
You should override 'get()' method in your 'detailpost' class, so that it would be something like below:
def get(self, request, *args, **kwargs):
queryset = self.model._default_manager.filter(user=request.user)
self.object = self.get_object(queryset)
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
It seems like it is the only way to pass User from Request instance to filter queryset.
I did not find that DetailView uses self.request

Django ModelForms: Seeding a FK related field in the form

I have a Workshop Django app. Each Workshop can have multiple attendees via a related WorkshopAttendee model (simplified below). I am using Django's ModelForm in a Class Based View, and in forms.py I am using crispy-forms:
models.py (relevant parts)
class Workshop(models.Model):
title = models.CharField(max_length=100)
information = models.TextField()
location = models.TextField()
class WorkshopAttendee(models.Model):
workshop = models.ForeignKey(Workshop)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
views.py (relevant parts)
from django.views.generic.edit import FormView
from workshop.forms import WorkshopAttendeeForm
class WorkshopAttendeeFormView(FormView):
def form_valid(self, form):
# Clean the data
form_data = form.cleaned_data
form.save(commit=False)
return super(WorkshopAttendeeFormView, self).form_valid(form)
forms.py
from django.forms import ModelForm
from workshop.models import WorkshopAttendee
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, HTML, Field, \
Fieldset, Button, Hidden, Submit, Reset
from crispy_forms.bootstrap import FormActions
# Create the form class
class WorkshopAttendeeForm(ModelForm):
def __init__(self, *args, **kwargs):
# Crispy form Layouts, Fieldsets, etc
super(WorkshopAttendeeForm, self).__init__(*args, **kwargs)
class Meta:
model = WorkshopAttendee
urls.py (relevant parts)
urlpatterns = patterns('',
url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<slug>[\w\-.]+)/(?P<action>[\w\-.]+)/$',
WorkshopAttendeeFormView.as_view(
form_class=WorkshopAttendeeForm,
success_url="success/",
template_name='workshop/workshop_registration.html',
), name='workshop-attendee-register'
),
url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<slug>[\w\-.]+)/$',
WorkshopDetailView.as_view(
context_object_name='workshop_detail',
queryset=Workshop.objects.select_related(),
template_name='workshop/workshop_detail.html',
), name='workshop-detail'
),
)
My question is, how can I seed the form with the workshop_id (i.e. the FK relation) in a hidden form field? Obviously because the form hasn't been submitted yet, there is no FK relation yet. But in the URL I have the kwarg of Workshop slug. I can hard-code the workshop_id as a Hidden() crispy-forms field on a per workshop basis, but this is totally unDRY. Any ideas? I don't think I can use the select_related() or prefetch_related() methods on the model in urls.py, so maybe I have to somehow get both models into the form view somehow?
I don't feel like this is an edge-case scenario, and I am sure someone else has had a similar app workflow.
Thanks in advance.
UPDATE
After further research it appears that I can do this, using Django Formsets. Not figured out exactly how yet..... hints welcome.
You don't need to pass the PK - you got it already in your URL as the slug.
So let's say this is your URL: http://example.com/workshops/awesome-workshop-slug/sign_in/
Your urls.py should look like this:
url(r'^workshop/(?P<workshop_slug>\w+)/sign_in/$',
# ../workshops/awesome-workshop-slug/sign_in/
view = 'workshop_signin',
name = 'workshop-signin',
),
Ok, in your views.py you're able to do this:
#login_required
def workshop_signin(request, workshop_slug, template='workshop/sign_in.html'):
"""Register user to workshop."""
form = WorkshopForm()
workshop = Workshop.objects.filter(slug=workshop_slug)[0]
if request.method == 'POST':
form = WorkshopForm(request.POST, instance=workshop)
if form.is_valid():
messages.info(request, 'Yay!')
kwargs = {
'workshop_form': form,
}
return render_to_response(template, kwargs, context_instance=RequestContext(request))
*untested quick and dirty code
Turns out I was overly complicating this.
All I needed to do was modify the form_valid method for Django's GCBV FormView to this:
workshop/views.py
from django.views.generic.edit import FormView
from workshop.models import Workshop, WorkshopAttendee
from workshop.forms import WorkshopAttendeeForm
from django.http import HttpResponseRedirect
class WorkshopAttendeeFormView(FormView):
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.workshop = Workshop.objects.filter(slug=self.kwargs['slug'])[0]
self.object.save()
return HttpResponseRedirect(self.get_success_url())
Which basically does not save the form on submit, but instead overrides it and first updates the object it is about to save (the WorkshopAttendee object) with the relevant Workshop (based on the Workshop slug field, which is unique), then saves the updated object (self.object.save) and kicks me to the success url.
Big thanks to #init3 for his helpful pointers. Much appreciated.

Django CreateView is not saving object

I'm practicing django Class-Based-View with a basic blog application.
For some reason, however, the CreateView for my Post model is not saving the post inside the database.
models.py
class Post(models.Model):
user = models.ForeignKey(User)
post_title = models.CharField(max_length=200)
post_content = models.CharField(max_length=500)
post_date = models.DateTimeField('date posted')
forms.py
class PostForm(forms.ModelForm):
class Meta:
model = Post
exclude = ('user', 'post_date')
views.py
class PostCreate(CreateView):
template_name = 'app_blog/post_save_form.html'
model = Post
form_class = PostForm
def form_valid(self, form):
form.instance.user = self.request.user
form.instance.post_date = datetime.now()
return super(PostCreate, self).form_valid(form)
It displays content without generating any error, but when I check the admin page,
the post created by the CreateView is not saved in the database..
Any idea..??
Thanks
One tip: don't use exclude when defining forms, use fields, is more secure and the recommended way to do it.
The redirect is defined by get_success_url method. If you have in your model the method get_absolute_url CreateView will redirect to that URL, otherwise you can always override get_success_url in your view.
Using get_absolute_url:
class Post(models.Model):
user = models.ForeignKey(User)
post_title = models.CharField(max_length=200)
post_content = models.CharField(max_length=500)
post_date = models.DateTimeField('date posted')
#permalink
def get_absolute_url(self):
return ('myurlname', (), {'myparam': something_useful})
Using get_success_url:
class PostCreate(CreateView):
template_name = 'app_blog/post_save_form.html'
model = Post
form_class = PostForm
def form_valid(self, form):
form.instance.user = self.request.user
form.instance.post_date = datetime.now()
form.save()
return super(PostCreate, self).form_valid(form)
def get_success_url(self):
return reverse('myurlname', args=(somethinguseful,))
I think you will find this page very useful when working with CBVs:
http://ccbv.co.uk/projects/Django/1.5/django.views.generic.edit/CreateView/
the problem is that you are excluding fields that are mandatory, so it won't pass through your form validation.
You should pass this fields hidden with some default value, let the use fill them, set them to null=True or populate them before you access form_valid
I came across this question today after many years but those answer seems not correctly.
The main issue here is the form.instance is None for CreateView. So my approach is below as suggestion form django docs:
def form_valid(self, form):
instance = form.save(commit=False)
instance.user = self.request.user
instance.post_date = datetime.now()
instance.save()
return redirect(self.get_success_url())
I think this is a simple case of not calling form.save(). When the form is validated, all of the checks are done, but it doesn't actually save the object in the database. To do that, you explicitly need to tell it to, via the save() method.
So you want:
class PostCreate(CreateView):
template_name = 'app_blog/post_save_form.html'
model = Post
form_class = PostForm
def form_valid(self, form):
form.instance.user = self.request.user
form.instance.post_date = datetime.now()
form.save()
return super(PostCreate, self).form_valid(form)

Categories