Form variable for django CreateView - python

Heads-Up: I don't know if this is a duplicate, but all the questions that StackOverflow said to be similar are not mine.
Hi, I have a django model called Post (I am doing the usual 'blog' website, since I am still learning). I am creating a CreateView for it (django.views.generic) to create more posts.
My problem is that I want to pass in string as a context variable. This can be done with context_object_name or the function get_context_data. But, to create the form that CreateView automatically generates, it passes a context variable called form. Since I am passing my own context data, the CreateView's form context variable gets overwritten.
So, what I am asking is, what is the name of that form variable (if there is) that I can pass into the context data dictionary like {'string': my_string, form: createView_form_variable}.
CreateView:
class CreateBlogView(LoginRequiredMixin, CreateView):
model = Post
template_name = 'blogs/create_update_blogs.html'
fields = ['title', 'content']
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
def get_context_data(self):
return {'string': 'Create', 'form': This is the thing I need}
Any help on this would be appreciated - Thanks in advance.
P.S. Please comment if I have not made things clear

The default implementation of get_context_data will return a dictionary to be passed as a context. The problem with your implementation is that you aren't calling the super method and using it's returned value. super is used to call the same method of the parent class, hence you can write:
class CreateBlogView(LoginRequiredMixin, CreateView):
model = Post
template_name = 'blogs/create_update_blogs.html'
fields = ['title', 'content']
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs) # call super
context['string'] = 'Create' # add to the returned dictionary
return context

Related

How to record current user when a new data saved in Django? [duplicate]

I'm using a custom CreateView (CourseCreate) and UpdateView (CourseUpdate) to save and update a Course. I want to take an action when the Course is saved. I will create a new many-to-many relationship between the instructor of the new course and the user (if it doesn't already exist).
So, I want to save the Course as course, and then use course.faculty to create that new relationship. Where is the best place to make this happen?
I'm trying to do this in form_valid in the views, but I'm getting errors when trying to access form.instance.faculty bc the course isn't created yet (in CourseCreate). The error message is like:
"Course: ..." needs to have a value for field "course" before this many-to-many relationship can be used.
It's also not working in CourseUpdate. The Assists relationship is not created. Should I be trying this in the Form? But I'm not sure how to get the user info to the Form.
Thank you.
models.py
class Faculty(models.Model):
last_name = models.CharField(max_length=20)
class Course(models.Model):
class_title = models.CharField(max_length=120)
faculty = models.ManyToManyField(Faculty)
class UserProfile(models.Model):
user = models.OneToOneField(User)
faculty = models.ManyToManyField(Faculty, through='Assists')
class Assists(models.Model):
user = models.ForeignKey(UserProfile)
faculty = models.ForeignKey(Faculty)
views.py
class CourseCreate(CreateView):
model = Course
template_name = 'mcadb/course_form.html'
form_class = CourseForm
def form_valid(self, form):
my_course = form.instance
for f in my_course.faculty.all():
a, created = Assists.objects.get_or_create(user=self.request.user.userprofile, faculty=f)
return super(CourseCreate, self).form_valid(form)
class CourseUpdate(UpdateView):
model = Course
form_class = CourseForm
def form_valid(self, form):
my_course = form.instance
for f in my_course.faculty.all():
a, created = Assists.objects.get_or_create(user=self.request.user.userprofile, faculty=f)
return super(CourseUpdate, self).form_valid(form)
The form_valid() method for CreateView and UpdateView saves the form, then redirects to the success url. It's not possible to do return super(), because you want to do stuff in between the object being saved and the redirect.
The first option is to not call super(), and duplicate the two lines in your view. The advantage of this is that it's very clear what is going on.
def form_valid(self, form):
self.object = form.save()
# do something with self.object
# remember the import: from django.http import HttpResponseRedirect
return HttpResponseRedirect(self.get_success_url())
The second option is to continue to call super(), but don't return the response until after you have updated the relationship. The advantage of this is that you are not duplicating the code in super(), but the disadvantage is that it's not as clear what's going on, unless you are familiar with what super() does.
def form_valid(self, form):
response = super(CourseCreate, self).form_valid(form)
# do something with self.object
return response
I would suggest to use Django's Signal. That is an action that gets triggered when something happens to a Model, like save or update. This way your code stays clean (no business logic in the form-handling), and you are sure that it only gets triggered after save.
#views.py
from django.dispatch import receiver
...
#receiver(post_save, sender=Course)
def post_save_course_dosomething(sender,instance, **kwargs):
the_faculty = instance.faculty
#...etc
If you need to modify also the Course object when call save function use False and after change save the object
def form_valid(self, form):
self.object = form.save(False)
# make change at the object
self.object.save()
return HttpResponseRedirect(self.get_success_url())
It is possible to do return super() as it is in the django doc:
https://docs.djangoproject.com/en/4.0/topics/class-based-views/generic-editing/
def form_valid(self, form):
# This method is called when valid form data has been POSTed.
# It should return an HttpResponse.
form.send_email()
return super().form_valid(form)

Django, CreateView: pass form argument to reverse_lazy

To redirect the user after filling out the CreateView form I would like to access an argument from the form and pass it to the reverse_lazy function.
How can I access the parameters of the form within CreateView?
I actually use the argument I'm looking for to pass it to the form itself (self.request.META.get('HTTP_REFERER').split('/')[-1]), but seem not to be able to use this logic in reverse_lazy.
get_form_kwargs also seems not to achieve the result:
views.py
class PieceInstanceCreate(LoginRequiredMixin, CreateView):
model = PieceInstance
fields = ['version', 'piece_image', 'status']
def form_valid(self, form):
obj = form.save(commit=False)
obj.piece = Piece.objects.get(id=self.request.META.get('HTTP_REFERER').split('/')[-1])
return super(PieceInstanceCreate, self).form_valid(form)
def get_form_kwargs(self):
kwargs = super(PieceInstanceCreate, self).get_form_kwargs()
return kwargs['piece']
success_url = reverse_lazy('piece-detail', kwargs={'pk': get_form_kwargs(self)})
urls.py
path('piece/<int:pk>', views.PieceDetailView.as_view(), name='piece-detail')
You don't pass it to reverse_lazy. Instead of using success_url, you should define the get_success_url method, which allows you to create the URL dynamically using whatever parameters you want.
However there are few other things wrong with your code here. Firstly, you should not be trying to do all that calculation based on the HTTP_REFERER attribute. If your view needs a piece of information, you should pass it in the URL as a keyword parameter, which you can then get in your view by using self.kwargs. In your case it looks like your view already has the pk argument; you can use self.kwargs['pk'].
Given that, your get_success_url method would look like:
def get_success_url(self):
return reverse('piece-detail', kwargs={'pk': self.kwargs['pk']})
Secondly, your get_form_kwargs method will always give a KeyError; the super method won't return a dictionary with a "piece" key, and even if it did the method must return a dict, not an individual value, including all the relevant items like the actual POST data. Again it's not clear what you are trying to do with this method; since you don't specify a custom form, it doesn't need custom kwargs. You should remove this method altogether.
Finally, you don't need to call form.save() inside your form_valid method, even with commit=False. A CreateView already assigns an instance to the form, so you can just do form.instance.piece = ....
Here the reworked and working class (using the inputs from #DanielRoseman):
class PieceInstanceCreate(LoginRequiredMixin, CreateView):
model = PieceInstance
fields = ['version', 'piece_image', 'status']
def form_valid(self, form):
form.instance.piece = Piece.objects.get(id=self.kwargs['pk'])
return super(PieceInstanceCreate, self).form_valid(form)
def get_success_url(self):
return reverse_lazy('piece-detail', kwargs={'pk': self.kwargs['pk']})
You don't need to do that when you use CBV
Just see this example:
models.py
class Author(models.Model):
name = models.CharField(max_length=200)
def get_absolute_url(self):
return reverse('author-detail', kwargs={'pk': self.pk})
views.py
class AuthorCreate(CreateView):
model = Author
fields = ['name']

How to modify the author choices field in my generic CreateView with authors(fk) only from the current user?

This is my code, i have read the documentations and it seems this method is the right way, i get no errors but i see no results. Can somebody help me in what i am doing wrong?
class BookCreate(LoginRequiredMixin, CreateView):
model = Book
fields = ['title', 'isbn', 'year', 'author', 'publisher']
def form_valid(self, form):
form.instance.owner = self.request.user
return super(BookCreate, self).form_valid(form)
def form_valid(self, form):
b = Book.objects.all
form.instance.author = ModelChoiceField(queryset=b.author_set.filter(owner=self.request.user))
return super(BookCreate, self).form_valid(form)
It is much easier to simply exclude the author from the list of fields, then set it in the form_valid method:
class BookCreate(LoginRequiredMixin, CreateView):
model = Book
fields = ['title', 'isbn', 'year', 'publisher']
def form_valid(self, form):
form.instance.owner = self.request.user
return super(BookCreate, self).form_valid(form)
If you do this, make sure you delete your second form_valid method, which is replacing the correct form_valid method above.
If you must include author as a field with a single option, then the code is much more complicated. You need a custom form with an __init__ method which takes user and sets the queryset for the auth field.
Then you need to modify your view to use your custom form, and override get_form_kwargs to include self.request.user.

Using current user to initialize ForeignKey user in Django CreateView

I'm getting an error:
AttributeError at /courses/create/
'CourseStudentForm' object has no attribute 'user'
When I try to create a new object by setting it's user field to the current user:
class CourseStudentCreate(CreateView):
model = CourseStudent
fields = ['semester', 'block', 'course', 'grade']
success_url = reverse_lazy('quests:quests')
#method_decorator(login_required)
def form_valid(self, form):
data = form.save(commit=False)
data.user = self.request.user
data.save()
return super(CourseStudentCreate, self).form_valid(form)
This is the model:
class CourseStudent(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
semester = models.ForeignKey(Semester)
block = models.ForeignKey(Block)
course = models.ForeignKey(Course)
grade = models.PositiveIntegerField()
active = models.BooleanField(default=True)
The form displays correctly, but when I submit I get the error.
ANSWER:
From here:
Pass current user to initial for CreateView in Django
If I want to keep user as a required field, it works if I change form_valid to:
def form_valid(self, form):
form.instance.user = self.request.user
return super(CourseStudentCreate, self).form_valid(form)
The cause of the error is described by Burhan Khalid below.
The reason it doesn't work is because you are missing a required field from your form class; recall that model form validation will also validate the model instance:
Validation on a ModelForm
There are two main steps involved in validating a ModelForm:
Validating the form
Validating the model instance
In your class, the inherited post method is calling is_valid():
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance with the passed
POST variables and then checked for validity.
"""
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
You can see that it only calls form_valid() if is_valid() returns true; in your case it can't return true because you have a required attribute missing.
You can solve this problem easily by making the user foreign key optional in your model.

Send form data from views to template

Edit:
I want the 'success_url' (ie, result.html) to display the 'data' from 'form.process()'. The following code obviously doesn't work.
Can anyone please tell me what's wrong with it or suggest another way to basically view the context 'data' in a template (either in the form of list or dict), ie a better way to display data to the user after a form has been submitted.
Many thanks in advance.
-- urls.py --
url(r'^$', view='main_view'),
url(r'^result/$', view='result_view'),
-- views.py --
class ResultView(TemplateView):
template_name = "result.html"
class MainView(FormView):
template_name = 'index.html'
form_class = UserInputForm
success_url = 'result/'
def form_valid(self, form):
data = form.process()
return super(MainView, self).form_valid(form)
def get_context_data(self, **kwargs):
context = super(MainView, self).get_context_data(**kwargs)
context['data'] = data
return context
main_view = MainView.as_view()
result_view = ResultView.as_view()
As far as I understood your question, you want to show the contents of the user submitted form in the result view. Is that correct?
In this case the method get_context_data won't help you at all, because it will only store data in the current context which is in MainView.
The form_valid method of FormView will make a HttpResponseRedirect to the success_url. So the question now is, how can we give the data to this view.
As explained in Django return redirect() with parameters the easiest way would be to put the data into the session. In the result.html-template you could then access this data as explained in Django: accessing session variables from within a template?
Here is the code:
class ResultView(TemplateView):
template_name = "result.html"
class MainView(FormView):
template_name = 'index.html'
form_class = UserInputForm
success_url = 'result/'
def form_valid(self, form):
self.request.session['temp_data'] = form.cleaned_data
return super(MainView, self).form_valid(form)
in the result.html template you could then access this temp_data so:
{{ request.session.temp_data }}
As suggested above, you can override get_context_data.
For example, you can do something like the below:
def get_context_data(self, **kwargs):
context = super(MainView, self).get_context_data(**kwargs)
#set some more context below.
context['foo'] = bar
...
return context
Look for get_context_data in context the Django class-based view docs. The dict returned by the overridden method will be passed into the templates.
There are a couple of things that could be your problem. First, in form_valid() method, you process the form before you call that class' parent form_valid(). Also, you're no storing the result in a common place for both methods to grab it. Try something like:
def form_valid(self, form):
self.data = form.cleaned_data
return super(MainView, self).form_valid(form)
def get_context_data(self, **kwargs):
context = super(MainView, self).get_context_data(**kwargs)
context['data'] = self.data
return context

Categories

Resources