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.
Related
I'm learning Django and I'm struggling with the Class Based View of Django.
I would like to access the object attributes before the update in order to show the user what was the previous attributes.
Here my class view :
class GroupUpdateView(LoginRequiredMixin, UpdateView):
model = GroupName
fields = ['name', 'description']
template_name = 'dashboard/groups/group_update_form.html'
group_name_before_editing ="" #I wanted to write model.name here
#Overrive
def form_valid(self, form):
return super().form_valid(form)
def get_success_url(self):
messages.success(self.request, "The group %s was updated successfully" % (
self.group_name_before_editing))
return reverse_lazy('dashboard:group-update', args=(self.object.pk,))
In get_success_url(), I would like to show the user the previous name of the group that has been updated.
I tried also with the get_context_data() but was not able to obtain the result.
Could you help please ? How to get the current model attributes ?
class GroupUpdateView(LoginRequiredMixin, UpdateView):
...
group_name_before_editing ="" #I wanted to write model.name here
You can't put the code there, because it runs when the server starts and loads the module. You need to put the code inside a method that runs when Django handles the request.
I would suggest overriding the get_object method and setting the value at that point.
class GroupUpdateView(LoginRequiredMixin, UpdateView):
def get_object(self, queryset=None):
obj = super().get_object(queryset)
self.group_name_before_editing = obj.name
return obj
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)
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']
I have two separate class-based views and would like to keep their functionality, and have the two CBVs point to the same template (I'm trying to bring two separate forms into a single page).
More specifically, I am trying to subclass/combine this email view, and this password change view, so that I can have them point to the same template, as I ultimately would like both forms on the same page.
I've attempted to do this by subclassing them into my own views:
class MyEmailUpdateView(LoginRequiredMixin, EmailView):
template_name = 'account/account_settings'
success_url = reverse_lazy('settings')
def form_valid(self, form):
return super(SettingsUpdateView, self).form_valid(form)
class MyPasswordUpdateView(LoginRequiredMixin, PasswordChangeView):
template_name = 'account/account_settings'
success_url = reverse_lazy('settings')
def form_valid(self, form):
return super(SettingsUpdateView, self).form_valid(form)
But I am now finding out due to errors, one by one, that it appears nothing from the parent class is actually transferred over to my custom class unless I manually bring it in(success_url, methods, etc). Even then, the code from the original classes that I am subclassing is pointing elsewhere.
SO, when combining these two views, do I need to copy all of the original code into my custom subclass views?
Is this the proper way to accomplish it? How can I combine these two views? I'm ultimately looking for a way to have both of their forms on a single page in my own app. Is there possibly a simpler way to accomplish this using the library's provided templates?
You can use Django Multi Form View to combine many forms in view
After install you can use it like below:
class MultiFView(MultiFormView):
form_classes = {
'email_form' : AddEmailForm,
'change_password_form' : ChangePasswordForm
}
record_id = None
template_name = 'web/multi.html'
def forms_valid(self, forms):
email = forms['email_form'].save(commit=False)
email.save()
return super(MultiFView, self).forms_valid(forms)
and in template:
{{ forms.email_form.as_p }}
{{ forms.change_password_form.as_p }}
I think You can use the default class in django to a achieve the same result.
As far as i understood i got the scenario like this we have two django forms and we need it to be used in same template if that is the scenario we can use the LoginView from django.contrib.auth.views which has several customizable option like you can give the additional form like this
class LoginUserView(LoginView):
authentication_form = LoginForm
extra_context = {"register_form": RegistrationForm}
template_name = 'accounts/index.html'
it will be using the get context data method to update the form which you will be able to get in the template and use accordingly .If you are not wishing to use the code like this you can still use it like this
class MyEmailUpdateView(LoginRequiredMixin, EmailView):
form_class = EmailForm
template_name = 'account/account_settings'
success_url = reverse_lazy('settings')
def get_context_data(self, **kwargs):
context = super(MyEmailUpdateView, self).get_context_data(**kwargs)
context['password_reset_form'] = ResetPasswordForm
return context
def form_valid(self, form):
return super(MyEmailUpdateView, self).form_valid(form)
Then you can handle your post in the form valid accordingly as your requirement.
Hope that helps get back if you need any additional requirement.
This can be solved by implementing a (kind of) partial update method for your view.
Tools:
First protect your sensitive data:
sensitive_variables decorator:
If a function (either a view or any regular callback) in your code uses local variables susceptible to contain sensitive information, you may prevent the values of those variables from being included in error reports using the sensitive_variables decorator
sensitive_post_parameters() decorator:
If one of your views receives an HttpRequest object with POST parameters susceptible to contain sensitive information, you may prevent the values of those parameters from being included in the error reports using the sensitive_post_parameters decorator
Create some code after:
Use ModelForm to create a form with specific fields of interest for your view. Don't include the password field, because we will add it in a different way to suit our needs:
myapp/forms.py:
class PartialUpdateForm(forms.ModelForm):
new_password = forms.CharField(
required=False, widget=forms.widgets.PasswordInput
)
class Meta:
model = MyUser
fields = ('email', 'other_fields',...)
Here you can use exclude instead of fields if you want to keep every other field except the password: ex. exclude = ('password',)
Use an UpdateView to handle the hustle and override it's form_valid method to handle the partial update:
myapp/views.py:
class MyUpdateView(UpdateView):
template_name = 'account/account_settings'
form_class = PartialUpdateForm
success_url = reverse_lazy('settings')
#sensitive_variables('new_password')
#sensitive_post_parameters('new_password')
def form_valid(self, form):
clean = form.cleaned_data
new_password = clean.get('new_password')
if new_password:
form.instance.password = hash_password(new_password)
return super().form_valid(form)
Finally, create a url for that view:
myapp/urls.py:
...
url(r'^your/pattern/$', MyUpdateView.as_view()),
This way you can handle:
Email AND password update at the same time
Only email update.
Only password update.
Using code from this solution: Django: Only update fields that have been changed in UpdateView
My Django app models.py has the following class:
class Project(models.Model):
name = models.CharField(max_length=100)
...
I am using class-based views so my views.py file has the following class:
from django.views import generic
from django.views.generic.edit import CreateView
class ProjectCreate(CreateView):
model = Project
fields = ['name']
The HTTP form works perfectly and creates a new element in the database, but I need to call a function from an external python file upon the creation of a new instance of the class Project, the code I'm trying to run is:
import script
script.foo(self.object.name)
I'm trying to run the function foo inside the class ProjectCreate but I'm clueless, I tried using get and dispatch methods but it didn't work, I have read the documentation of CreateView but I couldn't find my answer.
Should I use function-based views? or is there a solution for class-based views?
Thank you very much.
You probably want to do this inside the model save method, rather than in the view, so it will be called whenever a new instance is created:
class Project(models.Model):
...
def save(self, *args, **kwargs):
if not self.pk:
script.foo(self)
return super(Project, self).save(*args, **kwargs)
If you're sure you only want to do it from that one view, then you could override the view's form_valid method instead:
class ProjectCreate(CreateView):
def form_valid(self, form):
response = super(ProjectCreate, self).form_valid(form)
script.foo(self.object)
return response
If you want to run your function only inside the view class you can simply override a form_valid method:
class ProjectCreate(CreateView):
model = Author
fields = ['name']
def form_valid(self, form):
result = super().form_valid(form)
script.foo(self.object.name)
return result
If you want to run the function after an every instance creation you can use signals or override model methods.