Unable to Manually set Form Attributes with Crispy Forms - python

I've been working on creating a model form with django-crispy-forms, with Django 1.8.4 and django-crispy-forms-1.5.2. I am failing to alter the form tag attributes.
I have tried setting self.helper.form_tag = False, but it still produces a <form> tag. I've tried setting other attributes like the form_action, but this does not work either, the form tag remains unchanged (the final HTML is still just <form>).
In the views.py:
class RegisterStudentView(CreateView):
template_name = "register_student.html"
model = Student
form_class = StudentRegistrationForm
def form_valid(self, form):
form.save()
return HttpResponseRedirect('dashboard')
In the forms.py:
class StudentRegistrationForm(ModelForm):
def __init__(self, *args, **kwargs):
super(StudentRegistrationForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = False
class Meta:
model = Student
exclude = ['is_active', 'is_overdue', 'personid', 'tertiary_cell']
Any help would be greatly appreciated.

As awwester commented, the problem was having a hard coded <form> tag around the crispy forms function:
<form>
{% crispy %}
</form>
Basically, crispy forms used the existing form tag and did not create the new one I wanted.

Related

How to use autocompleteselect widget in a modelform

I know there is a new feature in Django 2.0, which is AutocompleteSelect widget in ModelAdmin.
I am trying to use it in my custom modelForm but just failed.
Tried like this
#unit is the foreign key to the incident
class AccountForm(forms.ModelForm):
class Meta:
model = Invoice
...
...
widgets = { 'incident':widgets.AutocompleteSelect(Invoice._meta.get_field('incident').remote_field, admin.site)
}
...
#Invoice model
class Invoice(models.Model):
...
incident = models.ForeignKey(Unit, on_delete=models.CASCADE,null=True)
...
Hope anyone can help me.
Thanks
The AutocompleteSelect widget will not work outside of the admin site.
If you are using AccountForm in admin site you can use the following code:
class AccountForm(forms.ModelForm):
...
incident = forms.ModelChoiceField(
queryset= Unit.objects.all(),
widget=AutocompleteSelect(Invoice.incident.field.remote_field, admin.site),
)
...
class Meta:
model = Invoice
fields = [
'incident',
...
]
#admin.register(Invoice)
class InvoiceAdmin(admin.ModelAdmin):
form = AccountForm
AutocompleteSelect has 2 required args, rel and admin_site. The rel is used to extract the model used to query the data from and relates to an attribute on a ForeignKey or ManyToManyField. Since I wanted to use this on a field that wasn't actually a ForeignKey, I needed to override a few things to make it work:
class ClientAutocompleteSelect(AutocompleteSelect):
def get_url(self):
model = Client
return reverse(self.url_name % (self.admin_site.name, model._meta.app_label, model._meta.model_name))
class ClientChoiceField(forms.ModelChoiceField):
def __init__(self, queryset=None, widget=None, **kwargs):
if queryset is None:
queryset = Client.objects.all()
if widget is None:
widget = ClientAutocompleteSelect(None, admin.site) # pass `None` for `rel`
super().__init__(queryset, widget=widget, **kwargs)
def to_python(self, value):
return value # just return the client_id and not a Client object
class MyAdminForm(forms.ModelForm):
client_id=ClientChoiceField()
...
This requires that the end user has admin read access to the autocomplete endpoint of the model being queried. You may be able to do more hacking to change that get_url and use your own endpoint to give search results, though.
I spent a few hours trying to understand why my code (built on #Tim 's answer) would not work, until I stumble on a comment about missing references to css/js files.
Here is a complete working solution to use the AutocompleteSelect widget in any custom form for signed-in users having both 'staff' and 'view' access to the given model:
from django.urls import reverse
from django.contrib.admin.widgets import AutocompleteSelect
from django.contrib import admin
class UserAutocompleteSelect(AutocompleteSelect):
def get_url(self):
model = CustomUser
return reverse(self.url_name % (self.admin_site.name, model._meta.app_label, model._meta.model_name))
class UserChoiceField(forms.ModelChoiceField):
def __init__(self, queryset=None, widget=None, **kwargs):
if queryset is None:
queryset = CustomUser.objects.all()
if widget is None:
widget = UserAutocompleteSelect(None, admin.site) # pass `None` for `rel`
super().__init__(queryset, widget=widget, **kwargs)
class UserAutocompleteSelectForm(forms.ModelForm):
"""
for changing user on Play objects
using amdin module autocomplete
"""
user = UserChoiceField(
# queryset=CustomUser.objects.all(),
help_text=_('Select the user to replace the current one')
)
class Meta:
model = Play
fields = ('user', )
You can use the same, replacing CustomUser and Play by your own models
If (like me) this is not working out-of-the-box with the html template you're using, that means that you need to include the required css/js files to your template. Here is a simple way to do it :
Providing that the form is declared as such in the view:
form = UserAutocompleteSelectForm()
...
context = {
'form': form,
...
}
return render(request, 'users/change_user.html', context)
you should add the following lines to the html template to include the required css/js files:
{% block extrahead %}
{{ block.super }}
{{ form.media }}
{% endblock %}

Can't update User and UserProfile in one View?

I use UpdateView to update user account. User consists of User and UserProfile like this:
class UserProfile(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE,related_name='userprofile')
telephone = models.CharField(max_length=40,null=True)
Now, I've created a class UpdateView to be able to update for example UserProfile - telephone which works.
FORM:
class UserProfileUpdateForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('telephone',)
URLS:
url(r'^edit-profile$',view=views.UserUpdate.as_view(),name='user_update'),
VIEW:
# #login_required
class UserUpdate(UpdateView):
form_class = UserProfileUpdateForm
context_object_name = 'user_update'
template_name = 'auth/profiles/edit-profile.html'
success_url = 'success url'
def get_object(self,queryset=None):
return self.request.user.userprofile
def form_valid(self, form):
#save cleaned post data
clean = form.cleaned_data
self.object = form.save()
return super(UserUpdate, self).form_valid(form)
Now, I want to be able to change some attributes which belongs to User and some attributes which belongs to UserProfile.
I've already tried to change UserProfileUpdateForm fields variable but It does not work at all...
class UserProfileUpdateForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('telephone','model.user.first_name',) <- this does not work, I want to add to the form attribute 'first_name' which belongs to User, not UserProfile
Do you know what to do to be able to change telephone, first_name, last_name etc. using UpdateView?
UpdateView is only made to handle one model with no relations. However, the wonderful django-extra-views library provides CBVs for models and inline relations.
class UserProfileInline(InlineFormSet):
model = models.UserProfile
form = UserProfileUpdateForm
extra = 0
def get_factory_kwargs(self):
kwargs = super(UserProfileInline,self).get_factory_kwargs()
kwargs.update({"min_num": 1})
return kwargs
class UserCreate(CreateWithInlinesView):
model=User
inlines = [UserProfileInline]
form_class = UserForm
success_url = reverse('some-success-url')
# REQUIRED - fields or exclude fields of User model
template_name = 'your_app/user_profile_update.html'
Be sure to check out the documentation for information on the variables passed to your template and how to work with inline formsets.
You have to create second form for User as well. Then pass it to the same UpdateView as a second form_class.
Note*: you may need to override get and post methods for UpdateView. This SO answer might help.
Render both forms in one template under one <form> tag:
<form action="" method="post">
{% csrf_token %}
{{ first_form }}
{{ second_form }}
</form>

How do I pass a parent id as an fk to child object's ModelForm using generic class-based views in Django?

I am trying to use Django Generic Class-Based Views to build a CRUD interface to a two-model database. I have a working CRUD interface to the parent model, and am stuck trying to get the child Create working. For consistency with other Django examples, take the parent to be Author and the child to be Book. What is the simplest way to allow users to add Books to an Author?
In HTML terms, I think that I want to make a link on the Author detail page that includes the ID of the Author, have that ID be pre-set on the Book form, and then have the Book form processing use that ID as the PK of the Book. But I don't understand how to use Django to make this happen. I have read through https://docs.djangoproject.com/en/1.6/topics/class-based-views/generic-editing/, How do I use CreateView with a ModelForm, How do I set initial data on a Django class based generic createview with request data, and Set initial value to modelform in class based generic views, each of which seems to answer a slightly different question.
Here is the relevant code:
models.py
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
author = models.ForeignKey(Author)
title = models.CharField(max_length=500)
views.py
class BookCreate(CreateView):
form_class = BookForm
def get_success_url(self):
return reverse('myapp:author_read',args=(self.object.author.pk))
forms.py
class BookForm(forms.Modelform):
class Meta:
model = Book
urls.py
url(r'^(?P<pk>\d+)/$', AuthorRead.as_view(), name='author_read'),
url(r'^book/create/(?P<author_id>\d+)/$', BookCreate.as_view(), name='book_create'),
templates/myapp/author_detail.html
...
<p>Add a book</p>
...
templates/myapp/book_form.html
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Done">
</form>
Questions
1) How do I get the Author ID from the Book page URL to the Author form, and then processed correctly? With the sample code above, the Django debugger shows that it's present in this way:
View function Arguments Keyword arguments URL name
myapp.views.BookCreate () {'author_id': u'1234'} book_create
but I don't understand how to grab that variable out of the ... context? ... and put it into the form.
1a) Can I make it a url parameter instead of part of the URL itself, i.e., book/create?author=1234 instead of book/create/1234/? Or even make the whole thing a POST so that it's not part of the URL? Which is the best practice, and how is it done?
2) Once the variable is in the form, how can it be present as a hidden input, so that the user doesn't have to see it?
With the url that you defined in author_detail.html the author_id variable will be accessible in the view as self.kwargs['author_id']
# views.py
class BookCreate(CreateView):
...
def form_valid(self, form):
book = form.save(commit=False)
author_id = form.data['author_id']
author = get_object_or_404(Author, id=author_id)
book.author = author
return super(BookCreate, self).form_valid(form)
...
def get_context_data(self, **kwargs):
context = super(BookCreate, self).get_context_data(**kwargs)
context['a_id'] = self.kwargs['author_id']
return context
Then in your form you can add:
class BookForm(forms.Modelform):
...
def __init__(self, *args, **kwargs):
self.fields["author_id"] = forms.CharField(widget=forms.HiddenInput())
super(BookForm, self).__init__(self, *args, **kwargs)
Then in the template:
<input type=hidden name="author_id" value="{{ a_id }}">
The form_valid in the view should retrieve the id, get the appropriate author and set that author as the books author. The commit=False prevents the model getting saved at first while you set the author and calling super will result in form.save(commit=True) being called.
You could pass the author id to the form, here's some directions:
class BookForm(forms.Modelform):
author = None
class Meta:
model = Book
def __init__(self, *args, **kwargs):
self.author = kwargs.pop('author')
super(BookForm, self).__init__(*args, **kwargs)
def save(self, commit=True):
# your custom save (returns book)
class BookCreate(CreateView):
form_class = BookForm
def get_form_kwargs(self):
kwargs = super(BookCreate, self).get_form_kwargs()
kwargs['author'] = # pass your author object
return kwargs
I had a similar situation and, when doing the accepted answer steps I encountered 2 errors (I'm using Python 2.7):
object has no attribute 'fields' which was fixed by using answer to a similar question: https://stackoverflow.com/a/8928501/2097023 from #scotchandsoda:
...self.fields should be placed before calling super(...)
def __init__(self, users_list, **kw):
super(BaseWriteForm, self).__init__(**kw)
self.fields['recipients'].queryset = User.objects.filter(pk__in=users_list)
object has no attribute 'get' which was fixed using answer: https://stackoverflow.com/a/36951830/2097023 from #luke_dupin:
...this error can also be generated by incorrectly passing arguments in the init of a form, which is used for an admin model.
Example:
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(self, *args, **kwargs)
Notice the double passing of self? It should be:
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)

Readonly fields in django formset

I'm using modelformset factory to generate formset from model fields. Here i want to make only the queryset objects as readonly and other (extra forms) as non readonly fields
How can i achieve this?
AuthotFormSet = modelformset_factory(Author, extra=2,)
formset = AuthorFormSet(queryset=Author.objects.all())
In Above formset i wanted to display all the queryset objects as readonly, and remaining extra forms as non readonly fields. How can i achive this?
if i used,
for form in formset.forms:
form.fields['weight'].widget.attrs['readonly'] = True
This will convert all the forms (including extra) fields to readonly which i dont want.
And also i'm using jquery plugin to add form dynamically to the formset
I'd recommend specifying a form to use for the model, and in that form you can set whatever attributes you want to read only.
#forms.py
class AuthorForm(forms.ModelForm):
class Meta:
model = Author
def __init__(self, *args, **kwargs):
super(AuthorForm, self).__init__(*args, **kwargs)
if self.instance.id:
self.fields['weight'].widget.attrs['readonly'] = True
#views.py
AuthorFormSet = modelformset_factory(Author, extra=2, form=AuthorForm)
You can also put in your template :
{{form.management_form}}
{% for i in form %}
<p>{{ i.instance.readonly_field }}</p>
{{i.as_p}}
{% endfor %}
and not put the readonly_field in ModelForm.Meta.fields.
just need to check if the instance has id, like this:
if self.instance.id
before setting it as read-only
I used python long back. Hope this helps . But if you wish to control fields display using jquery
$('.class').attr('readonly', true);
or
$('#id').attr('readonly', true);

Change a Django form field to a hidden field

I have a Django form with a RegexField, which is very similar to a normal text input field.
In my view, under certain conditions I want to hide it from the user, and trying to keep the form as similar as possible. What's the best way to turn this field into a HiddenInput field?
I know I can set attributes on the field with:
form['fieldname'].field.widget.attr['readonly'] = 'readonly'
And I can set the desired initial value with:
form.initial['fieldname'] = 'mydesiredvalue'
However, that won't change the form of the widget.
What's the best / most "django-y" / least "hacky" way to make this field a <input type="hidden"> field?
This may also be useful: {{ form.field.as_hidden }}
If you have a custom template and view you may exclude the field and use {{ modelform.instance.field }} to get the value.
also you may prefer to use in the view:
field = form.fields['field_name']
field.widget = field.hidden_widget()
but I'm not sure it will protect save method on post.
edit: field with multiple values don't supports HiddenInput as input type, so use default hidden input widget for this field instead.
an option that worked for me, define the field in the original form as:
forms.CharField(widget = forms.HiddenInput(), required = False)
then when you override it in the new Class it will keep it's place.
Firstly, if you don't want the user to modify the data, then it seems cleaner to simply exclude the field. Including it as a hidden field just adds more data to send over the wire and invites a malicious user to modify it when you don't want them to. If you do have a good reason to include the field but hide it, you can pass a keyword arg to the modelform's constructor. Something like this perhaps:
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
def __init__(self, *args, **kwargs):
from django.forms.widgets import HiddenInput
hide_condition = kwargs.pop('hide_condition',None)
super(MyModelForm, self).__init__(*args, **kwargs)
if hide_condition:
self.fields['fieldname'].widget = HiddenInput()
# or alternately: del self.fields['fieldname'] to remove it from the form altogether.
Then in your view:
form = MyModelForm(hide_condition=True)
I prefer this approach to modifying the modelform's internals in the view, but it's a matter of taste.
For normal form you can do
class MyModelForm(forms.ModelForm):
slug = forms.CharField(widget=forms.HiddenInput())
If you have model form you can do the following
class MyModelForm(forms.ModelForm):
class Meta:
model = TagStatus
fields = ('slug', 'ext')
widgets = {'slug': forms.HiddenInput()}
You can also override __init__ method
class Myform(forms.Form):
def __init__(self, *args, **kwargs):
super(Myform, self).__init__(*args, **kwargs)
self.fields['slug'].widget = forms.HiddenInput()
If you want the field to always be hidden, use the following:
class MyForm(forms.Form):
hidden_input = forms.CharField(widget=forms.HiddenInput(), initial="value")
If you want the field to be conditionally hidden, you can do the following:
form = MyForm()
if condition:
form.fields["field_name"].widget = forms.HiddenInput()
form.fields["field_name"].initial = "value"
Example of a model:
models.py
from django.db import models
class YourModel(models.Model):
fieldname = models.CharField(max_length=255, default="default")
In your form, you can add widgets with ModelForm. To make it hidden add 'type': 'hidden' as shown below👇
forms.py
from .models import YourModel
from django import forms
class YourForm(forms.ModelForm):
class Meta:
model = YourModel
fields = ('fieldname',)
widgets = {
'fieldname': forms.TextInput(attrs={'type': 'hidden'}),
}
If you don't know how to add it to your views.py file, here is some videos about it.
If you use Function Based View:
https://www.youtube.com/watch?v=6oOHlcHkX2U
If you use Class Based View:
https://www.youtube.com/watch?v=KB_wDXBwhUA
{{ form.field}}
{{ form.field.as_hidden }}
with this jinja format we can have both visible form fields and hidden ones too.
if you want to hide and disable the field to protect the data inside. as others mentioned use the hiddenInput widget and make it disable
in your form init
example:
if not current_user.is_staff:
self.base_fields['pictureValid'].disabled = True
self.base_fields['pictureValid'].widget = forms.HiddenInput()
With render_field is easy
{% render_field form.field hidden=True %}
You can just use css :
#id_fieldname, label[for="id_fieldname"] {
position: absolute;
display: none
}
This will make the field and its label invisible.

Categories

Resources