Django, CreateView: pass form argument to reverse_lazy - python

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']

Related

Form variable for django CreateView

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

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)

Pass argument to Django form

There are several questions on stackoverflow related to this topic but none of them explains whats happening neither provides working solution.
I need to pass user's first name as an argument to Django ModelForm when rendering template with this form.
I have some basic form:
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.first_name = ???
super(MyForm, self).__init__(*args, **kwargs)
class Meta:
model = MyModel
fields = [***other fields***, 'first_name']
Here's my sample class-based view:
class SomeClassBasedView(View):
def get(self, request):
user_first_name = request.user.first_name
form = MyForm(user_first_name)
return render(request, 'my_template.html', {'form': form})
What do I pass to MyForm when initialising it and how do I access this value inside the __init__ method?
I want to use 'first_name' value as a value for one of the fields in template by updating self.fields[***].widget.attrs.update({'value': self.first_name}).
The solution is as simple as passing initial data dictionary to MyForm for fields you need to set initial value.
This parameter, if given, should be a dictionary mapping field names to initial values. So if MyModel class has a field my_field and you want to set its initial value to foo_bar then you should create MyForm object with initial parameters as follows:
form = MyForm(initial={'my_field': 'foo_bar'})
A link to Django documentation on this topic.
You can pass the parameter instance to your form like this:
obj = MyModel(...)
form = MyForm(instance=obj)
If obj has a user first name it will be attached to your form.

map each post to the user who posted it by foreign key in django?

I want to connect each post with the logged in user who posted it.
models.py
from django.conf import settings
from django.db import models
# Create your models here.
class Campagin(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, default=1)
title = models.CharField(max_length=120)
media = models.FileField()
description = models.TextField(max_length=220)
timestamp = models.DateTimeField(auto_now=False, auto_now_add=True)
updated = models.DateTimeField(auto_now=True, auto_now_add=False)
def __str__(self):
return self.title`
As you can see the posts were made by two different users, but the relation shows that it is made by the first user
this image shows the registered users..
Views.py
class NewCampagin(LoginRequiredMixin, CreateView):
template_name = 'campagin/new_campagin.html'
model = Campagin
fields = ['title','media','description']
def get_absolute_url(self):
return reverse('campagin:active_campagin')
Okay so CreateView allows you to specify the model and fields attributes to implicitly create a form for you. It's quite neat for quick form submissions but in your case, you will need to make some customizations before saving the Campaign object into the database (linking up the current logged in user).
As a result, you will need to create your own form first (create a file called forms.py which can be next to your views.py) and enter this code:
class CampaignForm(ModelForm): # Import ModelForm too.
def __init__(self, *args, **kwargs):
# We need to get access the currently logged in user so set it as an instance variable of CampaignForm.
self.user = kwargs.pop('user', None)
super(CampaignForm, self).__init__(*args, **kwargs)
class Meta:
model = models.Campaign # you need to import this from your models.py class
fields = ['title','media','description']
def save(self, commit=True):
# This is where we need to insert the currently logged in user into the Campaign instance.
instance = super(CampaignForm, self).save(commit=False)
# Once the all the other attributes are inserted, we just need to insert the current logged in user
# into the instance.
instance.user = self.user
if commit:
instance.save()
return instance
Now that we have our forms.py all ready to go we just need to modify your views.py:
class NewCampagin(LoginRequiredMixin, CreateView):
template_name = 'campagin/new_campagin.html'
form_class = forms.CampaignForm # Again, you'll need to import this carefully from our newly created forms.py
model = models.Campaign # Import this.
queryset = models.Campaign.objects.all()
def get_absolute_url(self):
return reverse('campagin:active_campagin') # Sending user object to the form, to verify which fields to display/remove (depending on group)
def get_form_kwargs(self):
# In order for us to access the current user in CampaignForm, we need to actually pass it accross.
# As such, we do this as shown below.
kwargs = super(NewCampaign, self).get_form_kwargs()
kwargs.update({'user': self.request.user})
return kwargs
What's actually happening with my POST requests under the bonnet??
Note: This is just extra information for the sake of learning. You do
not need to read this part if you don't care about how your class
based view is actually handling your post request.
Essentially CreateView looks like this:
class CreateView(SingleObjectTemplateResponseMixin, BaseCreateView):
"""
View for creating a new object instance,
with a response rendered by template.
"""
template_name_suffix = '_form'
Doesn't look that interesting but if we analyse BaseCreateView:
class BaseCreateView(ModelFormMixin, ProcessFormView):
"""
Base view for creating an new object instance.
Using this base class requires subclassing to provide a response mixin.
"""
def post(self, request, *args, **kwargs):
self.object = None
return super(BaseCreateView, self).post(request, *args, **kwargs)
we can see we are inheriting from two very important classes ModelFormMixin and ProcessFormView. Now the line, return super(BaseCreateView, self).post(request, *args, **kwargs), essentially calls the post function in ProcessFormView which looks like this:
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)
As you can see, your CreateView really just boils down to this small post function which simply gets a specified form and validates + saves it. There's 2 questions to ask at this point.
1) What does form = self.get_form() do since I didn't even specify my form?
2) What is self.form_valid(form) actually doing?
To answer the first question, self.get_form() essentially calls another function form_class = self.get_form_class() and this function is actually found in ModelFormMixin (the one where inherited from!):
def get_form_class(self):
"""
Returns the form class to use in this view.
"""
if self.fields is not None and self.form_class:
raise ImproperlyConfigured(
"Specifying both 'fields' and 'form_class' is not permitted."
)
if self.form_class:
return self.form_class
else:
if self.model is not None:
# If a model has been explicitly provided, use it
model = self.model
elif hasattr(self, 'object') and self.object is not None:
# If this view is operating on a single object, use
# the class of that object
model = self.object.__class__
else:
# Try to get a queryset and extract the model class
# from that
model = self.get_queryset().model
if self.fields is None:
raise ImproperlyConfigured(
"Using ModelFormMixin (base class of %s) without "
"the 'fields' attribute is prohibited." % self.__class__.__name__
)
# THIS IS WHERE YOUR FORM WAS BEING IMPLICITLY CREATED.
return model_forms.modelform_factory(model, fields=self.fields)
As you can see, this function is where your form was being implicitly created (see very last line). We needed to add more functionality in your case so we created our own forms.py and specified form_class in the views.py as a result.
To answer the second question, we need to look at the function (self.form_valid(form)) call's source code:
def form_valid(self, form):
"""
If the form is valid, save the associated model.
"""
# THIS IS A CRUCIAL LINE.
# This is where your actual Campaign object is created. We OVERRIDE the save() function call in our forms.py so that you could link up your logged in user to the campaign object before saving.
self.object = form.save()
return super(ModelFormMixin, self).form_valid(form)
So here we are simply saving the object.
I hope this helps you!
More information at https://docs.djangoproject.com/en/1.10/ref/class-based-views/generic-editing/#createview

Use url parameter in class based view Django

I have a view (CreateWorkRelationView) that makes use of the CreateView CBV. In the URL, a parameter is passed (user) that I need to refer to a lot. Is it possible to set the object user outside the functions in my class? So are you able to access kwargs from outside a function?
So I basically just want to add the following line to my class
user = get_object_or_404(Contact.pk=kwargs['user'])
At the moment however, that returns
NameError: name 'kwargs' is not defined
This is my class
class CreateWorkRelationView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
template_name = 'groups/group_form.html'
form_class = WorkRelationForm
model = WorkRelation
title = "Add a work relation"
success_message = "Workrelation was successfully created."
def form_valid(self, form):
user = get_object_or_404(Contact, pk=self.kwargs['user'])
form.instance.contact = user
return super(CreateWorkRelationView, self).form_valid(form)
def get_success_url(self):
return reverse_lazy('contacts:contact_detail', self.kwargs['user'])
The reason why I would like to do this, is:
I want to use this object in my title string.
I am going to add a couple of more functions, and they all need this object.
The way I managed to do this is to use a FormView.
In my urls.py i have
regex=r'^my/path/(?P<pk>\d+)$',
In my views
class MyCreateView(LoginRequiredMixin, FormView):
def form_valid(self, form):
data = self.kwargs['pk']
It works well.
No, that can't possibly work; you don't have a user, or kwargs, or even a request at the time the class is defined. You need to do this inside one of the methods called at request time; probably get_context_data or get_object.

Categories

Resources