I know who to replace the ManyToMany Widget in the django admin interface:
class SomeModelAdmin(admin.ModelAdmin):
filter_horizontal = ('users',)
But how to replace the default widget in all views?
I guess this could be implemented without changing one line in my code or in django.
It would be great if I could the something like the Author part (two boxes for a ManyToMany input) in this picture with a widget:
I can use ctrl + click, but my users can't. They want an easier way.
Is there a way to replace all select-multiple widgets without many changes in my code?
You can use a base class which ovride formfield_for_manytomany
from django.contrib.admin import widgets
class ManyToManyAdmin(admin.ModelAdmin):
def formfield_for_manytomany(self, db_field, request=None, **kwargs):
kwargs['widget']= widgets.FilteredSelectMultiple(
db_field.verbose_name,
db_field.name in self.filter_vertical
)
return super(admin.ModelAdmin, self).formfield_for_manytomany(
db_field, request=request, **kwargs)
class SomeModelAdmin(ManyToManyAdmin):
pass
If I do not understand your question, please clarify in the comments.
The Django Form Widgets Documentation offers several pre-fab widgets that you can use to specify how a field in your form is rendered.
Without modifying the code in my views, I can simply add a widget keyword arg to the appropriate form fields.
forms.py (v1)
class TestForm(forms.Form):
CHOICES = (
(1, "choice1"),
(2, "choice2"),
(3, "choice3"),
)
field = forms.ChoiceField(choices=CHOICES)
This renders as a dropdown select:
<select id="id_field" name="field">
<option value="1">choice1</option>
<option value="2">choice2</option>
<option value="3">choice3</option>
</select>
Now adding the widget keyword arg to my field:
forms.py (v2)
field = forms.ChoiceField(choices=CHOICES, widget=forms.CheckboxSelectMultiple)
This renders as a list of checkboxes:
<ul id="id_field">
<li>
<label for="id_field_0"><input id="id_field_0" name="field" type="checkbox" value="1" /> choice1</label>
</li>
<li>
<label for="id_field_1"><input id="id_field_1" name="field" type="checkbox" value="2" /> choice2</label>
</li>
<li>
<label for="id_field_2"><input id="id_field_2" name="field" type="checkbox" value="3" /> choice3</label>
</li>
</ul>
Edit
It is also possible to add widgets that are used by Django Admin. See Django multi-select widget? for more information about this. Simply import the appropriate widget:
from django.contrib.admin.widgets import FilteredSelectMultiple
and use that instead. Note that in this particular case you will also need to include the form's media to get the desired effect.
You need to replace default widgets in django forms.
This works for me:
from django import forms
from django.contrib.admin.widgets import FilteredSelectMultiple
# replace MultipleChoise in Forms
forms.MultipleChoiceField.widget = FilteredSelectMultiple("verbose name", is_stacked=False)
# replace MultipleChoise in ModelForms
forms.ModelMultipleChoiceField.widget = FilteredSelectMultiple("verbose name", is_stacked=False)
Add code above to file forms_setup.py in your main project
Then in settings.py import file forms_setup.py:
from .forms_setup import *
Related
So I am trying to change my form's model Datefield output to the Datepicker similar to DatepickerWidget in CreateView
The forms are generated using a html template:
{% for field in form %}
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<span class="text-danger small">{{ field.error }}</span>
</div>
<label class="control-label col-sm-2">{{field.label_tag}}</label>
<div class="col-sm-10">{{field}}</div>
</div>
{% endfor %}
Here is the Views with what I tried:
class newenv_form(generic.CreateView):
model = Environment
fields =['name', 'description', 'creation_date', 'status','status_update_date']
template_name = 'catalogue/new_env.html'
#Does not work
def get_form(self, form):
form = super(newenv_form, self)
form.fields['creation_date','status_update_date'].widget = forms.DateInput(attrs={'class':'datepicker'})
return form
Here is what worked but it is a dropdown datepicker that is limited in choices
def get_form(self):
'''add date picker in forms'''
from django.forms.extras.widgets import SelectDateWidget
form = super(EnvironmentCreateView, self).get_form()
form.fields['creation_date'].widget = SelectDateWidget()
return form
Note that I remove form_class which was causing problems
UPDATE: On Django 3.1, you can find SelectDateWidget within django.forms.widgets
Try to change the following line in the method get_form:
form = super(newenv_form, self)
to:
form = super(newenv_form, self).get_form(form)
And please follow the conventions and use PascalCase for class names in python.
You could call this class EnvironmentCreateView. Further generic view classes could be called for example EnvironmentListView, EnvironmentDetailView, EnvironmentUpdateView, EnvironmentDeleteView.
Using the same pattern for all your model classes will produce comprehensible code.
EDIT (2017-10-24):
Regarding your comment here is a further explanation. Although it is hard to give a correct remote diagnosis, I'd suggest the following changes:
class EnvironmentCreateView(generic.CreateView):
# class attributes ...
def get_form(self, form_class=None):
form = super(EnvironmentCreateView, self).get_form(form_class)
# further code ...
The essential changes are in bold. The class name is changed to meet the conventions. Also the parameter form is changed to form_class to meet the convetions, too. I emphasise conventions in particular, because it makes the code very comprehensible to other people familiar with the framework.
The important change is that form_class has the initial value None.
That should solve the problem with the error.
In the body of the method you call the parent method with super and write after that your custom code.
Please check the documentation for generic.CreateView. It inherits, among others, from generic.FormMixin. That is the class with the method get_form.
In Django, one applies CSS styling to class-based form fields in forms.py (or equivalent).
My question: is it impossible to do it any other way inside a Django project?
I'll accept the answer even if the answer is "it's impossible". Hacks and tricks are acceptable as well. Illustrative examples would be great.
p.s. here's an example of a Django form where I've styled in the class-based form:
class SampleForm(forms.Form):
description = forms.CharField(max_length=250)
def __init__(self,*args,**kwargs):
super(SampleForm, self).__init__(*args,**kwargs)
self.fields['description'].widget.attrs['class'] = 'btn bcg'
self.fields['description'].widget.attrs['style'] = 'background-color:#F8F8F8; width:98%; color: #1f8cad;'
self.fields['description'].widget.attrs['autocomplete'] = 'off'
You can use template tags.
css.py
from django import template
register = template.Library()
#register.filter(name='css')
def css(field, css):
return field.as_widget(attrs={"style":css})
in your template:
{% load css %}
{{ item.field|css: 'width: 100px' }}
the result could be
<input id="id_field" name="field" style="width: 100px" type="text" />
As you can see, in style is your variable (width: 100px). You can also do it with class.
I'm using Django 1.7 with bootstrap 3 and want to render each form field the same way, best utilizing bootstrap has-error fields:
<div class="form-group {% if form.field.errors %}has-error has-feedback{% endif %}">
<label class="control-label" for="field">{{ form.field.help_text }}</label>
<div class="input-group">
<span class="input-group-addon"><span class="some-icon"></span></span>
{% render_field form.field class+="form-control" placeholder=form.field.label id="end-date" aria-describedby="inputError2Status" %}
</div>
<label class="control-label" for="field">{{ form.field.errors.as_text }}</label>
</div>
As you can see, the moment the number of fields increase it becomes a LOT of work, not only to write it, but also to maintain it and refactoring becomes a hell... not really DRY. For each field I need to change the form.field variables and id="field" and the icon
How would I write a function, templatetag or something else to make django render all the fields in my form this way? Is it even possible to do this? It would be awesome if the Form in django would have a setting to accept an optionalglypycon class which the renderer will use to render the icon.
-- EDIT --
As indicated by #Serafeim django-crispy-forms could be the solution. But how do I use crispy-forms to produce the above html?
-- EDIT 2 --
After reading up on crispy-forms I managed to come up with the following solution:
My forms.py file looks like this:
class CreateForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(CreateForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.form_action = 'submit'
self.helper.add_input(Submit('submit', 'Submit'))
self.helper.html5_required = True
self.helper.layout = Layout(
PrependedText('fieldA', '<span class="fa fa-calendar"></span>', placeholder="yyyy-mm-dd"),
PrependedText('fieldB', '<span class="fa fa-clock-o"></span>', placeholder="0:00:00 (h:m:s)"),
'fieldC')
fieldA = forms.DateField(required=True)
fieldB = FDurationField(required=True)
fieldC = forms.CharField(widget=forms.Textarea(attrs={'rows':3}), required=False, max_length=128)
class Meta:
model = AModel
exclude = ('fieldD', 'FieldE', 'fieldF', 'fieldG')
and in my template I only have to call {% crispy form %} which is much better than my initial version. However, I still feel like I have to repeat certain steps, like adding the fields to the Layout... Is it possible to reduce the code even further?
It would be awesome if the Form in django would have a setting to
accept an optional glypycon class which the renderer will use to render
the icon.
Forms are just a collection of fields. A Field is a combination of a widget (which control the HTML rendered) and validation rules.
To control what is rendered on the page, you'd modify the widget.
For example, if I want to specify a css class or a placeholder, I would do this at the widget level:
email = forms.EmailField(label='Email Address',
required=False,
widget=forms.TextInput(attrs={'class': 'form-control',
'placeholder': 'user#example.com'}))
You can create your own custom widgets and fields and obtain fine control over how forms are rendered; so what you are asking is absolutely possible; but as this is something that is often repeated, django-crispy-forms has become the defacto standard way of getting this done.
I'm learning Django Framework, and I have a question. To help you understand I will try and explain using the example below:
Suppose that we have some table in db as is:
CREATE TABLE names (id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100));
And I have the form in Django Admin as is:
<form>
<textarea name="names"></textarea>
<input type="submit" name="sbt" value="Submit">
</form>
User entered something in the input names in the form and submitted it. Then a script catches this data and splits it into an array (str.split("\n")) and in cycle adding to table names!
And I many quetion:
How i can add form to Django Admin?
How i can catch form data and add this data to somethink table in database?
Thanks.
First of all you must create a django model.
Put this code in models.py.
class Names(models.Model):
name = models.CharField(max_length = 100)
Then you must create the admin model.
Put this code in admin.py.
class NamesAdmin(admin.ModelAdmin):
list_display = ['name']
# whatever you want in your admin panel like filter, search and ...
admin.site.register(Names, NamesAdmin)
I think it meet your request. And for split the names you can override save model method and split the names in there. But if you want to have an extra form, you can easily create a django model form.
Put the code somewhere like admin.py, views.py or forms.py
class NamesForm(forms.ModelForm)
class Meta:
model = Names
That's your model and form. So, if your want to add the form to django admin panel you must create a view for it in django admin. For do this create a view as common.
Put the code in your admin.py or views.py.
def spliter(req):
if req.method == 'POST':
form = NamesForm(req.POST)
if form.is_valid():
for name in form.cleaned_data['names'].split(' '):
Names(name = name).save()
return HttpResponseRedirect('') # wherever you want to redirect
return render(req, 'names.html', {'form': form})
return render(req, 'names.html', {'form': NamesForm()})
Be aware you must create the names.html and put the below code in you html page.
{% extends 'admin/base_site.html' %}
{% block content %}
<!-- /admin/names/spliter/ is your url in admin panel (you can change it whatever you want) -->
<form action="/admin/names/spliter/" method="post" >{% csrf_token %}
{{ form }}
<input type="submit" value="'Send'" >
</form>
{% endblock %}
This is your view and your can use it everywhere. But if you want only the admin have permission to see this page you must add this method too your NamesAdmin class.
def get_urls(self):
return patterns(
'',
(r'^spliter/$', self.admin_site.admin_view(spliter)) # spliter is your view
) + super(NamesAdmin, self).get_urls()
That's It. I hope this can help you.
I need to fetch the css classes for the fields on the forms i'm rendering within a template. Now according to the docs here when looping through a form, each item is supposed to be a BoundField instance. and also according to the docs here there should be a css_classes attribute of that instance i can access.
For the generic login view I created the usual registration\login.html template. However on the template I can't get any classes to be output and neither do the default labels (.label_tag) contain any classes.
heres the form inlude:
<div class="default_form">
{{form.non_field_errors}}
<br />
{% for field in form %}
{{field.css_classes}}
<div class="field_container">{{field.label_tag}}{{field}}</div>
<span class="field_help">{{field.help_text}}</span>
<div class="field_errors">{{field.errors}}</div>
<br />
{% endfor %}
have i done anything wrong here?
I need the classes for my validation javascripts to work.
If you're using django.contrib.auth.forms.AuthenticationForm or similar, this is probably because error_css_class and required_css_class (see docs) are not set on the Form class.
>>> from django.contrib.auth.forms import AuthenticationForm
>>> hasattr(AuthenticationForm, 'required_css_class')
False
>>> AuthenticationForm({})['username'].css_classes()
''
>>> class MyForm(AuthenticationForm):
... error_css_class = 'error'
... required_css_class = 'required'
...
>>> MyForm({})['username'].css_classes()
'required'
You can solve this by subclassing the form like in the example above.
Note that if you wish to use the standard login view, you'd need to pass your custom form via the authentication_form argument. E.g. in your urls.py:
(r'^accounts/login/$', 'django.contrib.auth.views.login',
{'authentication_form': MyForm}),