Django Forms - change the render multiple select widget - django

In my model I have a manytomany field
mentors = models.ManyToManyField(MentorArea, verbose_name='Areas', blank=True)
In my form I want to render this as:
drop down box with list of all
MentorArea objects which has not
been associated with the object.
Next to that an add button which
will call a javascript function
which will add it to the object.
Then under that a ul list which has
each selected MentorArea object with
a x next to it which again calls a
javascript function which will
remove the MentorArea from the
object.
I know that to change how an field element is rendered you create a custom widget and override the render function and I have done that to create the add button.
class AreaWidget(widgets.Select):
def render(self, name, value, attrs=None, choices=()):
jquery = u'''
<input class="button def" type="button" value="Add" id="Add Area" />'''
output = super(AreaWidget, self).render(name, value, attrs, choices)
return output + mark_safe(jquery)
However I don't know how to list the currently selected ones underneath as a list. Can anyone help me? Also what is the best way to filter down the list so that it only shows MentorArea objects which have not been added? I currently have the field as
mentors = forms.ModelMultipleChoiceField(queryset=MentorArea.objects.all(), widget = AreaWidget, required=False)
but this shows all mentors no matter if they have been added or not.
Thanks

For me the functionality you described sounds a lot like what you can achieve with using the ModelAdmin' filter_horizontal and filter_vertical settings.
The widget they render lives in django.contrib.admin.widgets.FilteredSelectMultiple. You should have a look at its code!

Related

How to make elided pagination work in django-tables2

I am trying to use django-tables2 v2.5.2 to render a table of products.
Pagination renders correctly, it basically looks like:
['prev',1,2,3,4,5,'...',18,'next']
each button is tag with href='?page=x' and after click it updates url with the page parameter but ellpsis button ('...') has href='#' and obviously does nothing when clicked on. Other pages buttons works fine.
Do you have any idea how to setup django and/or django-tables2 to make elided pagination work?
I am trying to setup simple table with products. I am using function view and RequestConfig, like this:
views.py
def product_list(request: HttpRequest):
all_items = Product.objects.all().order_by("-publish_date")
product_filter = ProductFilter(request.GET, queryset=all_items)
table = ProductTable(product_filter.qs)
RequestConfig(request, paginate={"per_page": 5}).configure(table)
return render(
request, "product/product_list.html", {"table": table, "filter": product_filter}
)
and my tables.py code looks like this:
class ProductTable(tables.Table):
image_url = ImageColumn(verbose_name="Image")
publish_date = tables.DateColumn(format="d M Y", order_by=["publish_date"])
user = tables.Column(verbose_name="Verified by")
title = tables.Column(verbose_name="Product")
class Meta:
model = Product
template_name = "django_tables2/bootstrap.html"
sequence = ("title", "producer", "user", "status", "publish_date", "image_url")
exclude = ("id", "image_url")
filterset_class = ProductFilter
I tried to read documentation to django-tables2 and django framework but I think I must be missing something.
I am expecting the ellipsis button in pagination to load missing page range, so I guess it needs to have href set to something else than '#'.
EDIT:
I think I found the solution that works for me and most likely is what I was supposed to do.
The ellipsis button should not be clickable in the first place, its href='#' is set to correct value.
I styled the button with css to make it unclickable via pointer-events:none, changed cursor to look like its not clickable.
Basically I made the pagination look like and behave like pagination on StackOverflow (check bottom of the page here https://stackoverflow.com/questions)
I think I found the solution that works for me and most likely is what I was supposed to do.
The ellipsis button should not be clickable in the first place, its href='#' is set to correct value.
I styled the button with css to make it unclickable via pointer-events:none, changed cursor to look like its not clickable.
Basically I made the pagination look like and behave like pagination on StackOverflow (check bottom of the page here https://stackoverflow.com/questions)

Passing a variable back to a template and updating a dropdown box value

I have a blog and would like to add a post_type variable which will be a dropdown on the webpage.
I have added post_type to my Post model as a Charfield. And setup the dropdown in the template. (this might not be the best way to do this)
It works when I'm creating a Post and also when I edit the post, if I change the dropdown value, the new value is saved. The problem I'm having is when I'm editing a post, I can't get the value to be selected in the dropdown.
I think the html tag for the value in the dropdown needed to be market as Selected but I cant figure out how to do this. I'd really appreciate the help if someone can point me in the right direction.
The most simple way will be add list of choice to your charfield in the model.
model.py
class BlogPost(models.Model):
POST_TYPE_CHOICES = (
('cooking', 'Cooking'),
('story','Amazing Stories'),
)
#other fields here
post_type = models.CharField(choices=POST_TYPE_CHOICES, max_length=50)
Then if you create a ModelForm using this model the default layout in the template will be a dropdownlist.
from the doc :
choices
Field.choices
An iterable (e.g., a list or tuple) consisting itself of iterables of
exactly two items (e.g. [(A, B), (A, B) ...]) to use as choices for
this field. If choices are given, they’re enforced by model validation
and the default form widget will be a select box with these choices
instead of the standard text field.
EDITED the 20/02:
You need to pass the instance inside your view (I assume you are not using classbased view).
So you should have some thing like this:
def edit_post(request, post_id):
#try to get the instance of the post you need to edit
post_instance = get_object_or_404(Post, id = post_id)
#get your form and pass it the current Post instance
form = EditPostForm(request.POST or None, instance=post_instance)
#validate your form
if form.is_valid():
#if using ModelForm the database will be saved as well
form.save()
#then render your template with the form
return render(request, "edit_post.html", {'form': form})
Then you should use the {{form.field_name}} notation in your template and see the curent value with the dropdown without problem.

Make Django build contenteditable div for TextField instead of textarea

In my Django 1.10 project, I have a model:
class Contact(models.Model):
notes = models.TextField()
...and ModelForm:
class ContactForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ContactForm, self).__init__(*args, **kwargs)
for field_name, field in self.fields.items():
field.widget.attrs['class'] = 'form-control input-sm plain'
if field.required == True:
field.widget.attrs['required'] = ''
class Meta:
model = Contact
fields = ('notes',)
I have two questions regarding this:
Can I make Django render the notes field as div with contenteditable=true rather than textarea?
If yes, how do I automate the form.save() method?
The second question is a bit vague, so I would be grateful for a hint regarding the first question. I have read through the doc, but couldn't find relevant section :(
Question 1: Render a field with a specific HTML tag
In this case, <div contenteditable="true">...</div>
You can customize the widget used to render a form field. Basically, when you declare the field, you have to pass the argument widget=TheWidgetClass. Django has a lot of builtin widgets you can use. But you can also define your own. To know how, you will find many resources on the Internet. Simply search for "django create custom widget" on a search engine, or on SO. Possible answer: Django: How to build a custom form widget?
Since Django doesn't provide any official documentation on how to create custom widget, the smartest way would be to create a class inheriting Django's TextArea and using a custom html template based on original one.
Question 2: Automate form.save() method with such a custom widget
Basically, you have to make sure the value of the div tag is sent with other inputs values to the target view of your form, in POST data. In other words, you have to make sure the content of the div acts like the content of any form field.
Maybe this SO question can help you: one solution could be using JavaScript to copy the content of your div to a hidden textarea tag in the form when the user click on submit button.
Good luck ;)
First one can be done by using jquery. Since django will load the textarea with id='id_notes'. So after that you can select the element and do whatever you want to.
And in second one you can redefine the save method by writing you own save function in forms.py Here is an example for a url shortener.
Save method basically, defines what you want to execute when something is being committed to database.

How to render django form differently based on what user selects?

I have a model and a form like this:
class MyModel(models.Model):
param = models.CharField()
param1 = models.CharField()
param2 = models.CharField()
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ('param', 'param1', 'param2')
Then I have one drop down menu with different values and based on what value is selected I'm hiding and showing fields of MyForm. Now I have to take one step further and render param2 as a CheckboxInput widget if user selects a certain value from a drop down but in other cases it should be standard text field. So how would I do that?
I know this post is almost a year old, but it took me multiple hours to even find a post related to this topic (this is the only one I found, which came up as related when submitting my own question), so I felt the need to share my solution.
I wanted to have a form that would show and require a text field if an option from a dropdown menu matched a value stored in another model. I had a foreignKey relation between two models and I passed an instance of Model1 into the ModelForm for Model2. If a value chosen for a variable in Model2 matched a variable already set in Model1, I wanted to show and require a textfield. It was basically a "choose Other and then enter your own description" scenario.
I did not want the page to reload (I was trying to have this work in both mobile and desktop browsers with the least delay/reloads and using the same code for both), so I could not use the mentioned multiple forms loading in a view option. I started trying to do it with AJAX as suggested above when I realized I was over thinking the problem.
The answer was using JS and clean methods in the form. I added a non-required field (field1) that was not in Model2 to my Model2Form. I then hid this using jQuery and only displayed it (using jQuery) if the value of another field (field2) matched the value of the variable from Model1. To make that work, I did decide to have a hidden < span > in my template with the pk of the variable so I could easily grab it with jQuery. This jQuery worked perfectly for hiding and showing the field correctly so the user could choose the "other" value and then decided to choose a different one instead (and go back and forth endlessly).
I then used a clean method in my Model2Form for field1 that raised a ValidationError if no value was entered when the value in field2 matched my Model1 variable. I accessed that variable by using "self.other = Model1.variable" in my __ init __ method and then referencing that in the clean_field1 method.
I would have liked to have been able to accomplish this without having to hide and show a field with JS, but I think the only solutions for doing so with views or ajax caused delays/reloads that I did not want. Also, I liked the general simplicity of the method I used, rather than having to figure out how to pass partial forms back and forth through the HTTPRequest.
Update:
In my situation, I was creating entries for lost and found items and if the location where the item was found was not a provided option, then I wanted to show a textbox for the user to enter the location. I created a location object that was set as the "other" location and then displayed the textbox when that object was selected as the "found" location.
In forms.py, I added an extra CharField and use a clean method to check if the field is required and then throw a ValidationError if it wasn't filled in:
class Model2Form(forms.ModelForm):
def __init__(self, Model1, *args, **kwargs):
self.other = Model1.otherLocation
super(Model2Form, self).__init__(*args, **kwargs)
...
otherLocation = forms.CharField(
label="Location Description",
max_length=255,
required=False
)
def clean_otherLocation(self):
if self.cleaned_data['locationFound'] == self.other and not self.cleaned_data['otherLocation']:
raise ValidationError("Must describe the location.")
return self.cleaned_data['otherLocation']
Then in my JavaScript, I checked if the value of the "found" location was the "other" location (the value of which I had in a hidden span on my html page). I then used .show() and .hide() on the textbox's parent element as necessary:
$("#id_locationFound").change( function(){
if ($("#id_locationFound").val() == $("#otherLocation").attr("value")){ //if matches "other" location, display textbox; otherwise, hide textbox
$("#id_otherLocation").parent().show();
}else
$("#id_otherLocation").parent().hide();
});
Your best guess would be to trigger a "POST" request when you select something from your drop down menu.
The Value of that "POST" has to correspond your values you use to determine which field you would like to output.
Now you will actually need two forms:
class MyBaseForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ('param', 'param1', 'param2')
class MyDropDownForm(MyBaseForm):
class Meta:
widgets = {
'param2': Select(attrs={...}),
}
So as you can see the DropDownForm has been derived from MyBaseForm to make sure it will have all the same properties. But we have modified the widget of one of the fields.
Now you can update your view. Please note, this is untested Python + Pseudocode
views.py
def myFormView(request):
if request.method == 'POST': # If the form has been submitted...
form = MyBaseForm(request.POST)
#submit button has not been pressed, so the dropdown has triggered the submission.
#Hence we won't safe the form, but reload it
if 'my_real_submitbotton' not in form.data:
if 'param1' == "Dropdown":
form = MyDropDownForm(request.POST)
else:
#do your normal form saving procedure
else:
form = ContactForm() # An unbound form
return render(request, 'yourTemplate.html', {
'form': form,
})
This mechanism does the following:
When the form is submitted it checks if you have pressed the "submit" button or have used a dropdown onChange to trigger a submission. My solution doesn't contain the javascript code you need to trigger the submission with an onChange. I just like to provide a way to solve it.
To use the 'my_real_submitbutton' in form.data construct you will be required to name your submit button:
<input type="submit" name="my_real_submitbutton" value="Submit" />
Of course you can choose any string as Name. :-)
In case of a submit by your dropdown field you must check which value has been selected in this drop down menu. If this value satisfies the condition you want to return a Dropdown Menu you create an instance of DropDownForm(request.POST) otherwise you can leave everything as it is and rerender your template.
On the downside this will refresh your page.
On the upside it will keep all the already entered field values. So no harm done here.
If you would like to avoid the page refresh you can keep my proposed idea but you need to render the new form via AJAX.

How do I make a RadioSelect have a image as radio value?

I have the following form:
class ReviewForm(forms.ModelForm):
class Meta:
model = Review
widgets = {
'tipo' : forms.RadioSelect(),
}
But I want to use images as the values of my radio buttons, the image will vary according to the option, like this:
<input type="radio" id="id_tipo_0" value="UP" name="tipo" /><img src="/images/thumb_up.gif"/>
<input type="radio" id="id_tipo_1" value="DOWN" name="tipo" /><img src="/images/thumb_DOWN.gif"/>
I have no clues on how to achieve this.
There is a nice solution for this issue!
A ModelChoiceField has the method label_from_instance(self, obj). This method is called for every option in the ModelChoiceField.
You can overwrite ModelChoiceField.label_from_instance with:
def label_from_instance(obj):
"""
Shows an image with the label
"""
image = conditional_escape(obj.thumbnail_image.url)
title = conditional_escape(obj.title)
label = """<img src="%s" />%s""" % (image, title)
return mark_safe(label)
Note: you need to use mark_safe, otherwise the renderer will escape the img tag. Do apply the conditional_escape to the user input values (title and url).
If you want to use a regular ChoiceField you can just add HTML to the choices parameter and add mark_safe.
You can override RadioSelect (and RadioFieldRenderer) class.
OR! you can use jquery ( or something similar) to insert your img dynamically.
$(document).ready(function(){
$("#id_tipo_0").after('<img src="/images/thumb_up.gif"/>')
$("#id_tipo_1").after('<img src="/images/thumb_down.gif"/>')
});
If you want to use Django form rendering function, you'll have to use javascript to modifiy the DOM, and this will be a mess because the names of the option are rendered just after the input tag, not enclosed in any tag...
If your form does not have any other tags, go ahead, just write your input just as in your example, carefully using the Django names and values for the radio input, add a submit button, a CSRF token and that's all, you'll be able to validate your form in the view like if it was rendered via {{form.as_p}}