I'm trying to output the description field from my model
in a django form without joy.
After searching around and not finding what I need
I hope I can ask here.
Here is my models, form, template and template output.
I've abrieviated to help make this post concise.
I'm working on the view in this project so the model
has been designed by someone else and I can't really change it.
MODELS:
1)
from django.db import models
class Project(models.Model):
description = models.TextField(blank = True)
name = models.CharField(max_length = 255, blank = True)
def __unicode__(self):
""" String representation of projects. """
return unicode(self.name)
2)
from django.db import models
class Share(models.Model):
description = models.TextField
last_access = models.DateTimeField(auto_now = True)
location = models.URLField(verify_exists = False)
project = models.ForeignKey('Project', related_name = 'shares')
def __unicode__(self):
return unicode(self.location)
FORM:
from django import forms
from models import Share
class ExportForm(forms.Form):
ps = forms.ModelMultipleChoiceField(queryset=Share.objects.filter(project=1),widget=forms.SelectMultiple())
VIEW:
form = ExportForm()
TEMPLATE:
I have this to ouput the multiple select bos:
{{ form.ps }}
TEMPLATE OUTPUT:
<select multiple="multiple" name="ps" id="id_ps">
<option value="11">Share object </option>
<option value="10">Share object </option>
</select>
I've tried several things from searching around but can't
seem to be able to make the 'description' field in there rather than 'Share object'
Any advise much appreciated.
Thanks!
The easiest way to do this is to change the __unicode__ method of the Share model to return description instead of location, but since you say you can't change the model you will need to subclass ModelMultipleChoiceField and override the label_from_instance method.
class MyModelMultipleChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
return obj.description
This is explained in the documentation.
Related
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 %}
I have forked the django-oscar catalogue app to alter the models being used. Not in a major way, and not in a way that would affect pulling data from the database as far as I can see. This seems to be supported by the fact the the django-oscar dashboard still works fine and lets me add and view products. My models.py from my forked app:
from django.db import models
class Collection(models.Model):
name = models.CharField(max_length=50)
prod_category = models.CharField(max_length=50)
description = models.TextField()
manufacturer = models.TextField()
num_products = models.PositiveIntegerField()
image_url = models.URLField()
from oscar.apps.catalogue.abstract_models import AbstractProduct
class Product(AbstractProduct):
collection = models.ForeignKey(Collection, on_delete=models.CASCADE, null=True)
multiplier = models.DecimalField(max_digits=2, decimal_places=1, default='2.2')
from oscar.apps.catalogue.models import *
Here is my relevant view from my views.py
def product(request):
template = loader.get_template('/home/my_app/my_site/main_page/templates/main_page/product.html')
prods = Product.objects.values_list('categories')
context={'prods': prods}
return HttpResponse(template.render(context))
I tried loading from the built in model and my forked model (commenting and uncommenting one or both), neither makes a difference:
#from forkedoscarapps.catalogue.models import Product
from oscar.core.loading import get_class, get_model
Product = get_model('catalogue', 'product')
And the code I am using in the template to display data from the view:
{% for instance in prods %}
<li><{{ instance.name }}</li>
{% endfor %}
The resulting HTML is:
<li></li>
Which shows it is reaching the for loop, but for some reason no data is returned.
There is at least one category called beds, which displays fine in the django-oscar dashboard. What have I missed in my view?
edit: When I change instance.name to just instance I get the following returned in the HTML:
(1,)
So it is somewhat working, and showing what I assume is the primary key being returned, but why is the name of the field not being returned?
Product.objects.values_list('categories') yields a list of id tuples that represent the categories associated with the products in that queryset. That's not what you want to send to the template, you want to send instances, more specifically product instances if I'm not mistaken.
Do Product.objects.all() instead, and just use {{ instance.title }} in the template according to the definition of the oscar model: https://github.com/django-oscar/django-oscar/blob/master/src/oscar/apps/catalogue/abstract_models.py and to what ever you customised over it.
This is probably easy to solve. I created a form which use forms.ModelForm.
My model has ForeignKey field. Form creates a select field for foreignKey, but but does not display value correctly.
models.py
from django.db import models
# Create your models here.
class Gender(models.Model):
name = models.CharField(max_length=8, null=True)
class Meta:
db_table='gender'
class UserExample(models.Model):
name = models.CharField(max_length=16,null=True)
age = models.IntegerField(blank=True, null=True)
gender = models.ForeignKey('Gender', on_delete=models.SET_NULL, null=True)
class Meta:
db_table='userExample'
def __str__(self):
return ""
forms.py
from django import forms
from .models import UserExample, Gender
class UserForm(forms.ModelForm):
class Meta:
model = UserExample
fields = ('name', 'age', 'gender')
views.py
from django.shortcuts import render
from .forms import UserForm
# Create your views here.
def index(request):
form = UserForm
return render(
request,
'index.html',
{'form': form}
)
urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.index, name='index'),
]
index.html
<html>
<head>
<title>test </title>
</head>
<body>
<form action="formreturn/" method="post">
{{ form.as_p }}
<input type="submit" value="Submit" />
</form>
</body>
</html>
And after I launch my app. In select box I get only selection for gender objects but not for gender names.
Optional I used to add values using sqllite3 like this:
sqlite> insert into gender values(1,"Male");
sqlite> insert into gender values(2,"Female");
Implement __unicode__ or __str__ method in Gender model,
def __unicode__(self):
return '%s' % self.name
And it will display gender names in your option choices.
How to customize the default form field behavior of ForeignKey Model field
ForeignKey maps to ModelChoiceField and you can override the default behavior of the same. You can override the 'Select' option field value using 'to_field_name' parameter (useful when you have multiple unique fields in your related model) otherwise option field values are defaulted to pk field.
'empty_label' will change the default "--------" with the empty_label atrribute
forms.py
class UserForm(forms.ModelForm):
gender = forms.ModelChoiceField(queryset=Gender.objects.all(),
to_field_name = 'name',
empty_label="Select Gender")
class Meta:
model = UserExample
fields = ('name', 'age', 'gender')
Option display names are defaulted to __str__ method output. You can override the default behavior by writing a custom choice field class (inherited from ModelChoiceField) and override the label_from_instance() method.
def__str__(self):
return self.name
You will add this on your gender class
I already gave this answer in another place, hope it's not bad to copy paste.
In my case, I didn't wanna go make an str for my billion models, so I just did this:
You can make one custom ModelChoiceField to which you can pass a function. That way if you have different fields for which you want different attributes to be displayed, you can have only 1 class:
class CustomModelChoiceField(forms.ModelChoiceField):
name_function = staticmethod(lambda obj: obj)
def __init__(self, name_function, *args, **kwargs):
if not name_function is None: self.name_function = name_function
super(CustomModelChoiceField, self).__init__(*args, **kwargs)
def label_from_instance(self, obj):
return self.name_function(obj);
You can then call it as simply as this:
form_field = CustomModelChoiceField(
lambda obj: obj.get_full_name(),
queryset=Whatever.objects.all(),
)
You can also pass None in case you're doing some dynamic stuff and it'll just basically default to a regular ModelChoiceField. I'm not too much of a python guy but this works for me.
I've another quick question, I need to use very often a specific html representation for a model field. I know there is a lot of documentation for forms but what about simple model field ? For instance :
models.py
class Status (models.Model):
order = models.PositiveIntegerField(_(u'Order'))
name = models.CharField(_(u'Name'), max_length=255, unique=True, db_index=True)
color = models.CharField(_(u'Color'), max_length=6, null=True, blank=True)
template.html
{{status.order.as_span}} will be equivalent to <span>{{status.order}}<span>
The first idea I have is to implement a function inside a manager but seems to break the MVC rules ... There is a proper way for this ?
You shouldn't assemble html in your view code, this is what template tags are for. Put this in eg. <app>/templatetags/<app>tags.py (replace <app> with your app name):
from django.utils.safestring import mark_safe
from django import template
register = template.Library()
#register.simple_tag
def spanme(fld):
return mark_safe("<span>%s</span>" % fld)
and use it as:
{% load <app>tags %}
...
{% spanme status.order %}
It is possible to subclass the model field also:
from django.utils.safestring import mark_safe
from django.db.models.fields import CharField
from django.db import models
class SpanValue(unicode):
def as_span(self):
return mark_safe(u"<span>%s</span>" % self)
class SpanField(CharField):
__metaclass__ = models.SubfieldBase
def to_python(self, value):
if value is None:
return None
return SpanValue(value)
class SpanModel(models.Model):
foo = SpanField(max_length=25)
then you could use it like this:
from django import template
from maint.models import *
#val = SpanModel.objects.create(foo='bar')
val = SpanModel.objects.get(foo='bar') # must get it from the db for `to_python` to run
t = template.Template(u"""{{ val.foo.as_span }}""")
print t.render(template.Context({'val': val}))
which would print
<span>bar</span>
Doing something like this would be very surprising to anyone else, so please never ever do this :-)
ps: if you inherit from something that isn't a text field you might be able to override __unicode__ (so you wouldn't have to use .as_span in the template), however that is considerably more work.
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.