Django - get value of readonly field (fix readonly error) - django

I'm trying to edit fieldset.html template in order to add the hidden input under readonly div (in readonly case).
{% if field.is_readonly %}
<div class="readonly {% if field.field.name %} field-{{ field.field.name }}{% endif %}">{{ field.contents }}</div>
<input id="{% if field.field.name %} id-{{ field.field.name }}{% endif %}" type="hidden" value="{{ field.field.initial }}"/>
{% else %}
{{ field.field }}
{% endif %}
My problem is that if I set a field readonly with "get_readonly_fields", I can't submit the form because hidden field is required (I think this is a big error of django that uses div instead of the hidden input).
I tried to fix it with the code above but I'am not able to insert the value in to my field, because "field.field.initial" is empty for readonly field. How can I solve it?
UPDATE
My form.py:
class MyModelAdminForm(forms.ModelForm):
val1 = forms.CharField()
val2 = forms.ModelChoiceField(queryset=User.objects.filter(groups__name='Group1'))
val3 = forms.CharField(widget=forms.DateInput(attrs={'type': 'date'}))
class Meta:
model = MyModel
fields = ('val1', 'val2', 'val3')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self._meta.fields:
attrs = {'class':'form-control'}
self.fields[field].widget.attrs.update(attrs)
My admin.py
class CampaignAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
return MyModelAdminForm
def get_fieldsets(self, request, obj=None):
return [
(None, {'fields': ['val1', 'val2']}),
('Informations', {'fields': ['val3']}),
]
def get_readonly_fields(self, request, obj=None):
if obj:
return self.readonly_fields + ('val2',)
else:
return self.readonly_fields
I need that val2 is in readonly mode in edit page.

In general, you can add any fields to your form as you see fit. So, if you need the field to show up as read-only as well as a hidden field, you can just add two fields. You should name the hidden field correctly so that saving works. You can use label to change the label of the read-only field (https://docs.djangoproject.com/en/1.11/ref/forms/fields/#label).
EDIT - solution for your code:
For your solution, if you change the form to have val2 as readonly field instead of a regular field, you also need to change the form's Meta.fields setting from:
fields = ('val1', 'val2', 'val3')
to
fields = ('val1', 'val3')
Otherwise, the validators for val2 will run.
Or, you have to change the widget of val2 to HiddenInput and add its value as readonly_field using a different name with an explicit method in your django admin class. But that would require changing the fieldsets depending on the mode (edit or create).

Related

Django - why my widget is cleared out during save?

I'm struggling with a django custom widget used to present HTML data.
I've started with a template from standard django's textarea:
class Descriptionarea(forms.Widget):
template_name = "widgets/descriptionarea.html"
def __init__(self, attrs=None):
super().__init__(attrs
widgets/descriptionarea.html:
<textarea name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>
{% if widget.value %}{{ widget.value }}{% endif %}</textarea>
Which works fine as an element of my inline form:
class InlineForm(forms.ModelForm):
description = forms.CharField(widget=Descriptionarea)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
which is part of my TabularInline (django-nested-admin in use):
class TabularInline(NestedStackedInline):
form = InlineForm
fields = ("title", "description")
readonly_fields = ("title", )
model = MyModel
max_num = 0
I'd like to present the description as HTML document, so I've changed the template:
<details>
<summary>description</summary>
<pre name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>
{% if widget.value %}{{ widget.value | safe }}{% endif %}
</pre>
</details>
Unfortunately, it does not work, as now I cannot use save on admin pages, as Django complains about:
This field is required.
Indeed during save, the whole widget is cleared out.
Second question - when I add the description field to the list of readonly_fields, then it becomes a simple label. Is it possible to provide my own readonly widget?

queryset when building django form

I am trying to get specific querysets based when a customer-specific form loads, showing only that customer's name (embedded as an ID field), its respective locations and users.
The idea is to select one user and any number of locations from a multichoice box.
I've tried to pass the ID as a kwarg but am getting a KeyError. I've tried the kwarg.pop('id') as found on the web and same issue. Any advice?
forms.py
class LocGroupForm(forms.ModelForm):
class Meta:
model = LocationsGroup
fields = ('group_name', 'slug', 'customer', 'location', 'user_id',)
def __init__(self, *args, **kwargs):
qs = kwargs.pop('id')
super(LocGroupForm, self).__init__(*args, **kwargs)
self.fields['customer'].queryset = Customers.objects.get(pk=qs)
self.fields['location'].queryset = CustomerLocations.objects.filter(customer_id=qs)
self.fields['user_id'].queryset = CustomerUsers.objects.filter(customer_id=qs)
here is my view. it's just a generic view
views.py
class LocGroupCreate(LoginRequiredMixin, CreateView):
form_class = LocGroupForm
template_name = 'ohnet/a_gen_form.html'
the template is a dry template I use for all my forms- admittedly something I mostly stole from a tutorial
{% extends "ohnet/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
{% load static %}
<div class="container">
<h1>{{ title }}</h1>
<form method="POST">
{% csrf_token %}
{{ form|crispy }}
<input type="submit" name="submit" value="Submit">
</form>
</div>
{% endblock content %}
This is the KeyError from the form load.
You need to pass a value for the id when constructing the LocGroupForm, you can do that by overriding get_form_kwargs:
class LocGroupCreate(LoginRequiredMixin, CreateView):
form_class = LocGroupForm
template_name = 'ohnet/a_gen_form.html'
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['id'] = …
return kwargs
You will need to fill in the … that specifies the value passed as id to the form. This might for example be self.request.user.pk, or a URL parameter with self.kwargs['name-of-url-parameter']

Django_filters have initial value only when ?page=1 is present

I have the a view, AccoutList, which is trying to render a django_table2 table. The view's source code:
class AccountList(SingleTableMixin, FilterView):
model = Account
table_class = AccountTable
template_name = 'accounts/account_list.html'
context_table_name = 'object_list'
ordering = ['vps']
filterset_class = AccountFilter
This view is currently using this filterset (from django_filters):
import django_filters
from accounts.models import Account
class AccountFilter(django_filters.FilterSet):
class Meta:
model = Account
fields = ['is_suspended', 'is_abandoned']
is_suspended = django_filters.BooleanFilter(name='is_suspended', initial='False')
is_abandoned = django_filters.BooleanFilter(name='is_abandoned', initial='False')
def __init__(self, data=None, *args, **kwargs):
# if filterset is bound, use initial values as defaults
if data is not None:
# get a mutable copy of the QueryDict
data = data.copy()
for name, f in self.base_filters.items():
initial = f.extra.get('initial')
# filter param is either missing or empty, use initial as default
if not data.get(name) and initial:
data[name] = initial
super().__init__(data, *args, **kwargs)
Using this template:
{% if filter %}
<form action="" method="get" class="form form-inline">
{{ filter.form.as_p }}
<input type="submit" />
</form>
{% endif %}
{% render_table object_list %}
{% endblock %}
This is my from my urls.py
path('', login_required(AccountList.as_view())),
When I visit my page, 127.0.0.1:8000, I see that the filters are not set:
But then if i do 127.0.0.1:8000?page=1, I see the filters are initialized properly:
What is causing my filters to not have default value when I don't have page=1 appended to my url?
Initial value of BooleanFilter should be boolean, not string:
is_suspended = django_filters.BooleanFilter(name='is_suspended', initial=False)
is_abandoned = django_filters.BooleanFilter(name='is_abandoned', initial=False)

CreateView and related model fields with fixed inital values

I'm working with a CreateView where I know what some of the field values will be ahead of time. In the example below, I know that the author field for a new Entry object will be the current user and I use get_initial() to preset this.
Now I would like to omit this field from my template form. I've tried several approaches:
Simply commenting out the author field in the form template leads to an invalid form.
Leaving 'author' out of fields. Nope.
And here's a related problem. The example below involves a relationship to a User object that exists. But what if I need to create an object, say an auth Group for editors? I've tried creating a placeholder group and renaming it ... and, well, that didn't work very well.
#
# model
#
class Author(Model):
name = CharField(max_length = 60)
user = OneToOneField(to = User, related_name = 'author_user', on_delete = CASCADE)
class Entry(Model):
title = CharField(max_length = 60)
author = ForeignKey(to = Author, related_name = 'entry_author')
#
# view
#
class EntryCreateView(CreateView):
model = Entry
fields = ('title', 'author')
def get_initial(self):
initial = super(EntryCreateView, self).get_initial()
initial['author'] = get_object_or_404(Author, user = self.request.user)
return initial
#
# template
#
{% extends "base.html" %}
{% block content %}
<form action="" method="post">
{% csrf_token %}
<label for="{{ form.title.id_for_label }}">Title:</label>
{{ form.title }}
<label for="{{ form.author.id_for_label }}">Author:</label>
{{ form.author }}
<p>
<input type="submit" class="btn btn-primary" name="save" value="Save" />
<input type="submit" class="btn btn-primary" name="cancel" value="Cancel" />
</form>
{% endblock %}
You can manually set user in form_valid() method of EntryCreateView class:
class EntryCreateView(CreateView):
model = Entry
fields = ('title',)
def form_valid(self, form):
user = self.request.user
form.instance.user = user
return super(EntryCreateView, self).form_valid(form)
You'll need to create a ModelForm for the customizations you need (https://docs.djangoproject.com/en/1.9/topics/forms/modelforms/).
You can't remove author because it's required on your model currently.
Try something like this:
In forms.py...
class EntryForm(forms.ModelForm):
class Meta:
model = Entry
fields = ['title', 'author']
def __init__(self, *args, **kwargs):
initial = kwargs.get('initial', {})
self.author = initial.get('author')
super(EntryForm, self).__init__(*args, **kwargs)
You can make modifications to the fields (set to not required, delete a field from the form fields, etc) in __init__ or on the class.
Just import and reference this form in your views to use it.

Django - custom attributes for model fields

Is there a way in Django to add custom attributes to a model's fields (without resorting to subclassing fields)?
I would like to only display certain fields in certain sections of my template. This is because eventually each type of field will be displayed in a separate tab. I thought about adding a custom attribute to each field to identify which section/tab it should go in. But, so far, I've had no luck.
I have a few field types:
class Enum(set):
def __getattr__(self, name):
if name in self:
return name
raise AttributeError
FieldTypes = Enum(["one","two","three",])
And a few models:
class Model1(models.Model):
a = models.CharField()
b = models.ForeignKey('Model2')
c = models.IntegerField()
a.type = FieldTypes.one # this obviously doesn't work
b.type = FieldTypes.two # this obviously doesn't work
c.type = FieldTypes.three # this obviously doesn't work
class Model2(models.Model):
d = models.CharField()
And a form:
class Form1(forms.ModelForm):
class Meta:
model = Mode1
And a template:
{% for fieldType in FieldTypes %}
<div class="{{fieldType}}">
{% for field in form %}
{% if field.type = fieldType %}
{{ field }}
{% endif %}
{% endfor %}
</div>
{% endfor %}
But this doesn't work.
Ideas? Or other suggestions for only placing certain fields in certain sections of the page?
Thanks.
In general, I would keep this logic outside of the model class. Models shouldn't be tangled up with presentation elements if you can help it, and choosing which fields to display in a form seems like a presentation concern. Fortunately, the Form class provides a nice, UI-focused layer between the data layer (the model) and the presentation layer (the view and template).
Here's how I've addressed this in the past. In my Form class, I created a list of field groups, each with a title and a list of the names of the fields they contain:
class MyModelForm(forms.ModelForm):
field_groups = (
{'name':'Group One', 'fields':('a', 'b', 'c')},
{'name':'Group Two', 'fields':('d', 'e')},
)
class Meta:
model = MyModel
Then in the template, I looped through the groups, and within that loop conditionally included those fields:
{% for group in form.field_groups %}
<h3 class="groupheader">{{group.name}}</h3>
{% for field in form %}
{% if field.name in group.fields %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }}: {{ field }}
</div>
{% endif %}
{% endfor %}
{% endfor %}
This allows you to control the grouping and display of form fields within the MyModelForm class, which is a reasonable place for presentation logic to live.
It's posible!
class Model1(models.Model):
a = models.CharField()
b = models.ForeignKey('Model2')
c = models.IntegerField()
And a form:
class Form1(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(Form1, self).__init__(*args, **kwargs)
self.fields['a'].type = FieldTypes.one
self.fields['b'].type = FieldTypes.two
self.fields['c'].type = FieldTypes.three
class Meta:
model = Mode1
Turns out, that I do want this logic in my model class; the field types are used for more than just working out where/how to display them (though they are used for that too). I have come up with the following solution.
I defined some classes to store a set of field types:
class FieldType(object):
def __init__(self, type=None, name=None):
self._type = type
self._name = name
def getType(self):
return self._type
def getName(self):
return self._name
class FieldTypeList(deque):
def __getattr__(self,type):
for ft in self:
if ft.getType() == type:
return ft
raise AttributeError
FieldTypes = FieldTypeList([FieldType("ONE","The Name Of Field One"),FieldType("TWO","The Name Of Field Two")])
And a few models each of which has a set of mappings from field types to particular field names (In this example, fields 'a', 'b', and 'c' are of type 'ONE' and field 'd' is of type 'TWO'):
class ParentModel(models.Model):
_fieldsByType = {}
a = models.CharField()
b = models.CharField()
def __init__(self, *args, **kwargs):
super(ParentModel, self).__init__(*args, **kwargs)
for ft in FieldTypes:
self.setFieldsOfType(ft, [])
self.setFieldsOfType(FieldTypes.ONE, ['a','b'])
def setFieldsOfType(self,type,fields):
typeKey = type.getType()
if typeKey in self._fieldsByType:
self._fieldsByType[typeKey] += fields
else:
self._fieldsByType[typeKey] = fields
class ChildModel(ParentModel):
_fieldsByType = {} # not really sure why I have to repeat this attribute in the derived class
c = models.CharField()
d = models.CharField()
def __init__(self, *args, **kwargs):
super(ChildModel, self).__init__(*args, **kwargs)
self.setFieldsOfType(FieldTypes. ['c'])
self.setFieldsOfType(FieldTypes. ['d'])
I have a basic form:
class MyForm(forms.ModelForm):
class Meta:
model = ChildModel
And a custom filter to return all fields of a given type from a particular form (note, the accessing the model from the form via its Meta class):
#register.filter
def getFieldsOfType(form,type):
return form.Meta.model._fieldsByType[type.getType()]
And, finally, a template to pull it all together (the template takes MyForm and FieldTypes):
{% for type in types %}
<div id="{{type.getType}}">
{% with fieldsOfType=form|getFieldsOfType:type %}
{% for field in form %}
{% if field.name in fieldsOfType %}
<p>
{{ field.errors }}
{{ field.label_tag }}: {{ field }}
</p>
{% endif %}
{% endfor %}
{% endwith %}
</div>
{% endfor %}