Passing request object from view to form in Django - python

I'm trying to create an account edit page which visually contains a single form (ie single submit button) but the fields are part of two (or more) different models. I've pieced together a solution from several SO answers and the form loads fine on GET requests but I would now like to conditionally hide/display the terms of service checkbox field based on what url is being accessed. Specifically on registration the TOS should be displayed while it should not on the account edit page. Simplified code looks like so:
# views.py
class _RequestPassingFormView(FormView):
http_method_names = ['get', 'post', 'head', 'options', 'trace']
def get(self, request, *args, **kwargs):
# Pass request to get_form_class and get_form for per-request
# form control.
form_class = self.get_form_class(request)
form = self.get_form(form_class)
return self.render_to_response(self.get_context_data(form=form))
def post(self, request, *args, **kwargs):
# Pass request to get_form_class and get_form for per-request
# form control.
form_class = self.get_form_class(request)
form = self.get_form(form_class)
if form.is_valid():
# Pass request to form_valid.
return self.form_valid(request, form)
else:
return self.form_invalid(form)
def get_form_class(self, request=None):
return super(_RequestPassingFormView, self).get_form_class()
def get_form_kwargs(self, request=None, form_class=None):
return super(_RequestPassingFormView, self).get_form_kwargs()
def get_initial(self, request=None):
return super(_RequestPassingFormView, self).get_initial()
def get_success_url(self, request=None, user=None):
# We need to be able to use the request and the new user when
# constructing success_url.
return super(_RequestPassingFormView, self).get_success_url()
def form_valid(self, form, request=None):
return super(_RequestPassingFormView, self).form_valid(form)
def form_invalid(self, form, request=None):
return super(_RequestPassingFormView, self).form_invalid(form)
class AccountEditView(_RequestPassingFormView):
form_class = AccountEditForm
template_name = 'account_edit.html'
def form_valid(self, request, form):
success_url = self.get_success_url(request, new_user)
try:
to, args, kwargs = success_url
return redirect(to, *args, **kwargs)
except ValueError:
return redirect(success_url)
def get_success_url(selfself,request, user):
return '/account'
#forms.py
class CombinedFormBase(forms.Form):
form_classes = []
def __init__(self, *args, **kwargs):
super(CombinedFormBase, self).__init__(*args, **kwargs)
for f in self.form_classes:
name = f.__name__.lower()
setattr(self, name, f(*args, **kwargs))
form = getattr(self, name)
self.fields.update(form.fields)
self.initial.update(form.initial)
def is_valid(self):
isValid = True
for f in self.form_classes:
name = f.__name__.lower()
form = getattr(self, name)
if not form.is_valid():
isValid = False
# is_valid will trigger clean method
# so it should be called after all other forms is_valid are called
# otherwise clean_data will be empty
if not super(CombinedFormBase, self).is_valid() :
isValid = False
for f in self.form_classes:
name = f.__name__.lower()
form = getattr(self, name)
self.errors.update(form.errors)
return isValid
def clean(self):
cleaned_data = super(CombinedFormBase, self).clean()
for f in self.form_classes:
name = f.__name__.lower()
form = getattr(self, name)
cleaned_data.update(form.cleaned_data)
return cleaned_data
class RegistrationForm(forms.Form):
required_css_class = 'required'
email = forms.EmailField(label=_('E-mail'))
password1 = forms.CharField(widget=forms.PasswordInput,
label=_('Password'))
password2 = forms.CharField(widget=forms.PasswordInput,
label=_('Password (again)'))
"""
Conditionlly display TOS checkbox based on context
"""
def __init__(self, *args, **kwargs):
"""
add in a field for terms of service here if viewing
the registration form
"""
super(RegistrationForm, self).__init__(*args, **kwargs)
class AccountProfileForm(forms.Form):
required_css_class = 'required'
company = forms.CharField(label=('Company Name'))
class AccountEditForm(CombinedFormBase):
form_classes = [RegistrationForm, AccountProfileForm]
This is my first django project so it's possible that this is a completely wrong direction also. If so a hint towards a simpler solution would be appreciated.

As you are using class based view, I think you can try like this:
view:
class _RequestPassingFormView(FormView):
http_method_names = ['get', 'post', 'head', 'options', 'trace']
def get(self, request, *args, **kwargs):
form_class = self.get_form_class()
self.object = None
form = form_class(request_data=request)
return self.render_to_response(self.get_context_data(form=form))
Or like this:
class _RequestPassingFormView(FormView):
http_method_names = ['get', 'post', 'head', 'options', 'trace']
#No need to override the get method in this view.
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.update(request_data=self.request)
return kwargs
And form is like this:
class RegistrationForm(forms.Form):
#form fields
def __init__(self, *args, request_data=None, **kwargs):
super().__init__(*args, **kwargs)
print(request_data)
# do other operations

I'm gonna keep it super simple. If you need further details, please refer here:
def my_view(request):
if request.method == 'POST':
# Sending your request info as kwarg.
form = myform(request.POST, user=request.user)
class MyForm(forms.Form):
def __init__(self, *args, **kwargs):
# Recieving it.
self.user = (kwargs.pop('user', None))
super(MyForm, self).__init__(*args, **kwargs)

You may want to consider just having two separate forms. If the only difference is the one on the registration page has a terms of service checkbox, then that form class could inherit from the other class and have the extra form field.

Related

How to detailview pk in post method in DetailView? (Django)

In my detailView I have 2 methods get_context_data and post. In get_context_data I can get the detailView pk with self.object.pk but how can I get it in the post method?
[ updated ]
here is the view
class Class_detailView(LoginRequiredMixin, DetailView):
login_url = '/'
model = Class
template_name = "attendance/content/teacher/class_detail.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['attendance_form'] = AttendanceForm(current_class_pk=self.object.pk) # pass data to form via kwargs
return context
def post(self, request, *args, **kwargs):
if request.method == "POST":
attendance_form = AttendanceForm(request.POST)
if attendance_form.is_valid():
attendance_form.instance.teacher = self.request.user
attendance_form.save()
return redirect('class-detail', pk=self.kwargs.get('pk'))
form
class AttendanceForm(forms.ModelForm):
class Meta:
model = Attendance
fields = ['student',]
def __init__(self, *args, **kwargs):
current_class_pk = kwargs.pop('current_class_pk')
super(AttendanceForm, self).__init__(*args, **kwargs)
current_student = Class.objects.get(id=current_class_pk)
self.fields['student'].queryset = current_student.student
I want to get the pk and pass it to the form when the post request is called.
How can I do it?
did you try this:
def post(self, request, *args, **kwargs):
if request.method == "POST":
attendance_form = AttendanceForm(request.POST, current_class_pk=self.kwargs.get('pk'))
if attendance_form.is_valid():
attendance_form.instance.teacher = self.request.user
attendance_form.save()
return redirect('class-detail', pk=self.kwargs.get('pk'))

Django CBV - set form class based on permissions?

I have created two forms in forms.py one form has less fields than the other.
what I would like to now do is get the current users permissions and set the form class of the CBV based on those perms.
below is my current view:
class EditCircuit(UpdateView):
model = Circuits
# if user_passes_test(lambda u: u.has_perm('config.edit_circuit')))
form_class = CircuitForm
# else
# form_class = CircuitFormRestricted
template_name = "sites/circuit_form.html"
#method_decorator(user_passes_test(lambda u: u.has_perm('config.edit_circuit')))
def dispatch(self, *args, **kwargs):
self.site_id = self.kwargs['site_id']
self.site = get_object_or_404(SiteData, pk=self.site_id)
return super(EditCircuit, self).dispatch(*args, **kwargs)
def get_success_url(self, **kwargs):
return reverse_lazy("sites:site_detail_circuits", args=(self.site_id,))
def form_valid(self, form):
form.instance.site_data = self.object.site_data
return super(EditCircuit, self).form_valid(form)
def get_form_kwargs(self, *args, **kwargs):
kwargs = super().get_form_kwargs()
return kwargs
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['SiteID']=self.site_id
context['SiteName']=self.site.location
context['FormType']='Edit'
context['active_circuits']='class="active"'
return context
You can override the get_form_class method.
def get_form_class(self):
if self.request.user.has_perm('config.edit_circuit'):
return CircuitForm
return CircuitFormRestricted
Also, it looks as though you don't need the get_form_kwargs definition, as it's not doing anything at the moment.
you can create your own mixin like this
class AuthorOnlyMixin(object):
def has_permissions(self):
return self.get_object().created_by == self.request.user
then use it like this
class EditViewClass(AuthorOnlyMixin, EditView):
def get_form_class(self):
if self.has_permissions():
return FormWithPermission
else:
return FormWithoutPermission

Django: Different views for POST and GET - Form validation errors

I have a DemandDetailView(DetailView) and BidCreationView(CreateView).
On DemandDetailView page, there is a form (for creating Bids) which posts data to BidCreationView.
I can't figure out what to do in case form is invalid. I would like to render DemandDetailView again with form errors and preserve corresponding URL.
class DemandDetailView(DetailView):
model = Demand
template_name = 'demands/detail.html'
def dispatch(self, request, *args, **kwargs):
self.bid_creation_form = BidCreationForm(request.POST or None, request.FILES or None,request=request)
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['bid_creation_form']=self.bid_creation_form
return context
class BidCreationView(CreateView):
http_method_names = ['post']
model = Bid
form_class = BidCreationForm
def get_success_url(self):
return reverse_lazy("demands:demands")
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.update({'request': self.request})
return kwargs
def form_valid(self, form):
form.instance.demand_id = self.kwargs.pop('demand_id')
return super().form_valid(form)
Do you have any ideas? My only idea is to use session which isn't probably the best way.
You could use is_valid() method from Form Objects. Something like:
class DemandDetailView(DetailView):
model = Demand
template_name = 'demands/detail.html'
def dispatch(self, request, *args, **kwargs):
form = BidCreationForm(request.POST or None, request.FILES or None,request=request)
if form.is_valid():
self.bid_creation_form = form
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['bid_creation_form']=self.bid_creation_form
return context
Option #2 (Personal Choice):
forms.py
from django import forms
from .models import Bid
class BidCreationForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(BidCreationForm, self).__init__(*args, **kwargs)
for field in self.fields.values():
field.widget.attrs = {"class": "form-control"}
class Meta:
model = Bid
fields = ('user', 'demands', 'amount', 'transaction')
Take a look at the Meta class within the Form. Its explicitly calling the Bid Model and the fields attribute is referring Bid fields from the Model Instance. Now you could call this form in any view without calling another view. If you want to add logic to this form, like calculating total amount or something like that then should do it within the form also. Write code once, Dont Repeat yourself.

Python edit form data prior to django validation

I'm fairly new to python and have been searching for awhile to find how I can edit form data BEFORE all the standard python form/field validators do their magic.
I have a model form with an IntegerField which I'd like to remove the "$" and commas from (using some sort of custom validation), then let the normal to_python() validate() etc do their thing.
My code is below - any help would be much appreciated!
forms.py
class BuyerSettingsForm(forms.ModelForm):
total_offer_limit = forms.IntegerField(required=False, max_value=10000000, min_value=0)
def __init__(self, request, *args, **kwargs):
super(BuyerSettingsForm, self).__init__(*args, **kwargs)
class Meta:
model = Buyer
fields = ['total_offer_limit']
def save(self, commit=True):
profile = super(BuyerSettingsForm, self).save(commit=commit)
profile.total_offer_limit = self.cleaned_data['total_offer_limit']
profile.save()
return profile
views.py
class SettingsPreferences(LoginRequiredMixin, BuyerAccessRequiredMixin, BuyerAdminAccessRequiredMixin, UpdateView):
template_name = 'invoicely/buyer/settings/buyer_settings.html'
form_class = BuyerSettingsForm
success_url = reverse_lazy('settings_preferences')
def get_object(self, *args, **kwargs):
return self.request.user.profile.buyer
def get_initial(self):
ctx = super(SettingsPreferences, self).get_initial()
ctx.update({
'total_offer_limit': self.object.total_offer_limit,
})
return ctx
def get_form_kwargs(self):
kwargs = super(SettingsPreferences, self).get_form_kwargs()
kwargs['request'] = self.request
return kwargs
def form_valid(self, form):
self.object = form.save()
messages.add_message(self.request, messages.SUCCESS, "Settings successfully updated")
return super(SettingsPreferences, self).form_valid(form)
If you are already overloaded get_form_kwargs you can do this. This is data which your form will be initialized with. So we can edit it before its initialization.
class SettingsPreferences(LoginRequiredMixin, BuyerAccessRequiredMixin, BuyerAdminAccessRequiredMixin, UpdateView):
...
def get_form_kwargs(self):
kwargs = super(SettingsPreferences, self).get_form_kwargs()
kwargs = copy.deepcopy(kwargs)
kwargs['request'] = self.request
if self.request.method in ('POST', 'PUT'):
# here put your data editing code
kwargs['data']['total_offer_limit'] = int(kwargs['data']['total_offer_limit'].strip().replace('$', ''))
return kwargs

Django How to pre-populate a form field with a argument form context

I have the following view below. I would like to use 'email' from the get_context_data method to pre-populate a form field in my UserAuthenticationForm form, how is this possible?
View:
class CampaignLoginView(LoginView):
model = Campaign
form_class = UserAuthenticationForm
def get_context_data(self, *args, **kwargs):
context = super(CampaignLoginView, self).get_context_data(*args, **kwargs)
user = AppUser.objects.get(pk=1)
context['email'] = user.email
Form:
class UserAuthenticationForm(AuthenticationForm):
def __init__(self, *args, **kwargs):
super(UserAuthenticationForm, self).__init__(*args, **kwargs)
Or is there is a way 'I should be doing it'?
What you want to do is pass initial data to the form. Assuming that LoginView is a class-based view that includes the FormMixin, this is best done in the get_form_kwargs method:
def get_form_kwargs(self):
kwargs = super(CampaignLoginView, self).get_form_kwargs()
kwargs['initial']['email'] = self.request.user.email
return kwargs

Categories