Why can't I inject form into HTML? - django

I have a form that I want to inject into a class based DetailView.
forms.py
class PastLocationForm(forms.Form):
locations = forms.ModelChoiceField(queryset=Location.objects.all().order_by('location_name'))
views.py
class PatientDetailView(DetailView):
model=Patient
form_class = PastLocationForm
Unfortunately, the form PastLocationForm doesn't appear on the HTML page after injection. I inspected the page and there was nothing.
Interestingly, if I pass PastLocationForm to a functional view and render it for another page, the form shows up! I also have other views where I make use of "form_class" for other modelForms and they function correctly.
I will switch my view to functional view if I can't find the solution but I would rather keep the class based view.

The reason might be the fact that DetailView does not handle a form_class attribute (FormViews do), you'd need to use a mixin.
Check out this answer:
Django combine DetailView and FormView

Related

How to place captcha on Django CreateView that uses Material Design Layout() feature

I'm working in an existing codebase that uses Django Material. There is a CreateView defined with a Django Material Layout:
class OurModelCreateView(LayoutMixin, CreateView):
model = OurModel
layout = Layout(
Row('field1', 'field2', 'field3'),
Row(...)
)
This view is getting lots of spam signups and so needs to have a captcha. I use Django Recaptcha, and I've set up a number of captchas in the past. However, I've never set one up without using a ModelForm. If I create a Django model form and define the captcha field in the form as I've always done:
from captcha.fields import ReCaptchaField
from captcha.widgets import ReCaptchaV3
class OurModelForm(ModelForm):
captcha = ReCaptchaField(widget=ReCaptchaV3)
class Meta:
model = OurModel
exclude = ()
and then specify form_class = OurModelForm on the CreateView, the following error is raised by ModelFormMixin.get_form_class(): "Specifying both 'fields' and 'form_class' is not permitted". This error is being raised because, though I've not explicitly specified fields, Django Material's LayoutMixin defines fields: https://github.com/viewflow/django-material/blob/294129f7b01a99832a91c48f129cefd02f2fe35f/material/base.py (bottom of the page)
I COULD drop the Material Layout() from the CreateView, but then that would mean having to create an html form to render the Django/Django Material form - less than desirable as there are actually several of these CreateViews that need to have a captcha applied.
So I think that the only way to accomplish what I'm after is to somehow dynamically insert the captcha field into the form.
I've dynamicaly inserted fields into Django forms in the past by placing the field definition in the __init__() of the Django form definition, but I can't figure out what to override in either CreateView (or the various mixins that comprise CreateView) or Django Material's LayoutMixin in order to dynamically insert the captcha field into the form. The following several attempts to override get_form and fields in order to dynamically insert the captcha field do not work:
On the CreateView:
def get_form(self, form_class=None):
form = super(OurModelCreate, self).get_form(form_class)
form.fields['captcha'] = ReCaptchaField(widget=ReCaptchaV3)
return form
def fields(self):
fields = super().fields(*args, **kwargs)
fields['captcha'] = ReCaptchaField(widget=ReCaptchaV3)
return [field.field_name for field in fields
# fields is actually a list, so trying the following too, but it doesn't include the ReCaptchaField(widget=ReCaptchaV3) anywhere at this point
def fields(self):
fields = super().fields(*args, **kwargs)
fields.append('captcha')
return fields
Any help would be greatly appreciated.
Following up on the comment from #Alasdair above which pointed me to the answer, I solved this problem by removing Django Material's LayoutMixin from CreateView, creating a Django form with the captcha field defined, and then adding to CreateView the form_class for the Django form. Also see my last comment above. It was counterintuitive to me until I looked again at the code after #Alasdair's second comment: the use of LayoutMixin on the CreateView isn't necessary for the layout = Layout(...) on the CreateView to work.

Django ModelForm hide field from form and use value from url

Thanks in advance for reading this. I can't wrap my head around it and it's getting quite frustrating by now.
We have the following registration form:
class RegistrationForm(forms.ModelForm):
class Meta:
model = Register
fields = ('name', 'company_name')
def clean(self):
if is not self.cleaned_data.get('card').is_available():
raise forms.ValidationError(_('Error'))
The Register model includes a card linked to a Card model. This includes is_available() which functionally works.
Our flow is:
The end user selects the card which lists all registrations for it.
They click the 'Add registration'-button which brings them to cards/{PK}/add.
The Add registration-button is a generic.View. In post(self, request, pk) I have the following code:
form = RegistrationForm(request.POST)
But how do I pass it the contents of Card.objects.get(pk=pk) to it?
I tried:
data = request.POST.copy()
data['card'] = pk
form = RegistrationForm(data)
But I think because card is not included in fields it gets lost somewhere, which makes sense from a sanitize-all-input-point of view, but I would very much like to add the card dynamically, in this case.
Any ideas?
So, just use CreateView and study how it does things using the linked site.
There is no need to use generic.View as it's the basic of basics. You only want to implement all this logic using generic.View to get more familiar with the way things work or if you need some very special form handling.
The short version would be:
from django.views import generic
from myapp.forms import RegistrationForm
class CardCreateView(generic.CreateView):
form_class = RegistrationForm
ModelForm has a save method. The correct way to solve this is to use it with commit=False, that will return an object that hasn’t yet been saved to the database. Then you can alter that object before finally saving it.
This is explained here in the docs
So this is what your code should look like:
form = RegistrationForm(request.POST)
form.save(commit=False)
form.card = Card.objects.get(pk=pk)
form.save_m2m()
save_m2m should be used if your model has many-to-many relationships with other models. In my case, it was a OneToOne, so I used save() instead.
If you use a CreateView instead of the generic View, the snippet above should go into your overridden form_valid method

Dynamic get_absolute_url using url query parameters

Big picture: I'd like my reverse method in get_absolute_url (see below) to return a url with a query parameter appended to it at the end, e.g. <url>?foo=bar. Further, I'd like bar to be specified by the POST request that triggered the call to get_absolute_url, either as an input to the form (but not a field represented by the model, something temporary) or as a url query parameter. I am easily able to access bar in my view using either method, but I can't seem to figure out how to access it in my model.
The motivation here is that my detail page splits up the fields from my model into different tabs using javascript (think https://www.w3schools.com/howto/howto_js_tabs.asp). When the user is updating the model, they choose which tab they want to update, and then the update template only renders the fields from the model which are related to that tab. More importantly, after the user submits the update, I want the detail page to know to open the specific tab that the user just edited.
(I understand how this works if the field is a part of the model; in get_absolute_url with parameters, the solution is pretty straightforward and involves using self.id. In my case though, bar is not a part of the model and I can't figure out how else to access it)
Some specifics: I have a model in my project called Context. I have implemented a generic DetailView and an update page for the model using a modelform called ContextForm and a generic UpdateView called ContextUpdate. Once the form is submitted, I redirect to the detail page using get_absolute_url in models.py:
def get_absolute_url(self):
return reverse("context:review",kwargs={"slug": self.slug})
My urlpatterns in urls.py looks something like:
urlpatterns = [
url(r'^(?P<slug>[-\w]+)$',views.ContextDetail.as_view(),name="review"),
url(r'^(?P<slug>[\w]+)/edit$',views.ContextUpdate.as_view(),name="edit"),
]
I am able to access this parameter in my UpdateView quite easily:
def post(self,request,**kwargs):
print (request.POST.get("bar")) #accessing input to form
print (request.GET.get("bar")) #accesssing url parameter
return super().post(request,**kwargs)
But when get_absolute_url is called inside the model, it seems I no longer have access to it.
Any suggestions for how to accomplish this? I want to use get_absolute_url (along with modelforms, generic views, etc.) so that I can follow Django conventions, but it seems like using get_absolute_url is making the functionality that I want difficult to accomplish. If the redirect to the detail view following the POST request were to happen inside my view, then I would know how to solve this (I think). Any thoughts or ideas would be greatly appreciated!
As you say, you can't access the request inside your get_absolute_url method. Therefore you should override get_success_url, from which you can access it.
def get_success_url(self):
return reverse(reverse("context:review", kwargs={"slug": self.object.slug}) + '?bar=%s' % self.request.GET.get('bar')
Or if you want to re-use get_absolute_url:
def get_success_url(self):
return self.object.get_absolute_url + '?bar=%s' % self.request.GET.get('bar')
The second option is DRYer but would break if get_absolute_url was changed to include a querystring like ?foo=foo.

Add a form to admin side

I need to create a form to admin-side with two fields
Number of code: integer
Value of code: float
How can I do that. This form is not related to any model.
You can implement your modelless form as explained in #levi's answer.
Then, you can place it in the admin site in a number of different ways, depending your needs:
Make instances of the form available to all templates via a context processor, and override the admin templates to have it rendered wherever you want. You can create a view for only processing the form.
Create a view for both rendering and processing the form in a unique place, and hook it up to the admin as explained in the old Django Book, you'll need to make sure the template for that view extends one of the admin's templates (admin/change_form.html may be a good choice).
from django import forms
class Your_Form(forms.Form):
number_code = forms.IntegerField()
value_code = forms.FloatField()

Django Class-Based Generic Views and ModelForms

Like much documentation on generic views in Django, I can't find docs that explicitly describe how to use the new Class-Based Generic Views with Django Forms.
How is it done?
What have you tried so far? The class based views are pretty new, and the docs don't have a lot of examples, so I think you're going to need to get your hands dirty and experiment!
If you want to update an existing object, then try using UpdateView. Look at the mixins it uses (e.g ModelFormMixin, SingleObjectMixin, FormMixin) to see which methods you can/have to override.
Good luck!
The easiest way to use model forms with class based views is to pass in the model and keep a slug / pk captured in url, in which case you will not need to write any view code.
url(r'^myurl/$', CreateView.as_view(model=mymodel))
#Creates a model form for model mymodel
url(r'^myurl/(?<pk>\w+)/$', UpdateView.as_view(model=mymodel))
#Creates a model form for model mymodel and updates the object having pk as specified in url
url(r'^myurl/(?<slug>\w+)/$', DeleteView.as_view(model=mymodel, slug_field="myfield"))
#Creates a model form for model mymodel and deletes the object denoted by mymodel.objects.get(my_field=slug)
You can also override methods to obtain more complex logic. You can also pass a queryset instead of a model object.
Another way is to create a modelform in forms.py and then pass form_class to the url as
url(r'^myurl/$', CreateView.as_view(form_class=myform))
This method allows you to define form functions as well as Meta attributes for the form.