I'm wrestling with how to best create HTML pages in Django that can either be used for displaying or editing data. That is, I'd like the field's values to appear as text in display mode, but in their widgets when in edit/add mode. It appears that Django wasn't designed to do this: the fields always appear in their widgets (eg, text input, text area,
etc).
Is there a common technique for handling this, short of using forms for one, and not the other?
I was thinking of a custom templatetag filter that could be used for every form field, like:
{{ form.field_name|render_field:mode }}
where render_field would either return the field's HTML widget, or just the value as text, based on the mode.
Have I missed something, or is this a viable solution?
Answering my own question, apparently. I ended up with a three-part solution:
attach the model to the form, so I'll have the default display widgets for each field
write the form using a template tag, passing it the form.field, user, and action
write a render template tag to handle #2
Step one:
form.model = Model(...)
Step two:
{{form.field1.label}}
{% render form.field1 user action %}
{{form.field2.label}}
{% render form.field2 user action %}
Step three:
Something like:
def render(formfield, user, action, default_text="Private"):
if not user.is_authenticated():
action = "view"
if action == "view":
if user.is_authenticated():
fieldname = formfield.name
retval = str(getattr(formfield.form.model, fieldname))
else:
retval = default_text
else:
retval = formfield.as_widget()
return retval
Since you are saving the data, you must have a model attached to the form somehow, a modelform or not. So you can just use that model directly to get the values and render it in a template like you want.
The above suggestion would be possible, but since forms can be rather complex, it's probably not an easy task or worth the bother. Depends on how often you want to do this. But then it would probably be easier to create a filter for the model instead of the form.
Have I missed something
Forms not only display the field widgets but also handle the post data. A post sent would cause it to process the data cleanup, form and field error handling, etc.
It's kind of breaking the pattern - why create and render a form object only to tweak it not to look like a form?
If you are worried about too much work on templates, try to solve it with template inheritance in a best possible way. If you are very sure you want to change only the field tag, you can make sth like
{% if form %}
{% for error in form.field.errors %}
{{ error|escape }}
{% endfor %}
{{ form.field }}
{% else %}
{{ object.field }}
{% endif %}
for every field, but IMO that's not the point, YMMV.
Also what comes to mind (thinking of your solution) is dynamically attach widgets to form fields, but that'd be overengineering.
I have the same problem. Right now I have separate templates for display and editing; the former renders object fields, the latter form fields (but sometimes also object fields, for things which are not editable). The HTML structure can be quite complex: for example, on some pages I have large tables representing a multi-level hierarchy of objects. As a result, I end up with a large amount of duplicated code in the two templates, which is the opposite of DRY.
I have used template filters with form fields before, to save code when displaying errors together with the field. I think it would be a viable solution in this case. Another possibility may be to use a ModelForm subclass which can be told to render uneditable versions of the fields. This would help keep the templates simple. You could even render both the static and the editable version of a field, and use JavaScript to switch between them, activating and deactivating an editing mode.
Related
django forms is giving me a pretty bad headache...
I've been struggling with this for 2 hours now.
I wanted to make a custom form for a model to be able to add objects to it and also be able to move the fields as I want to (instead of using form.as_p (or form|crispy), I wanted to put the entire form in a grid-like table using a custom pattern, not only putting one under another) but whenever I put a choicefield (which is either a choicefield or a foreignkey) their values are empty and also, 3 out of 4 formfields are actually shown, one remains the default.
It contains fields='__all__' with 3 choicefields and 1 floatfield each with a label, nothing more.
To show the forms in html I used
{% for field in form.visible_fields %}
{{ field.label_tag }}
{{ field.errors }}
{{ field }}
{{ field.help_text }}
{% endfor %}
which works well. Am I trying to solve the problem in a wrong way? I'll come back to this topic tomorrow, I'll need some rest now but I don't understand why passing a choices=TheModel # or # choices=TheModel.ojbects.all() breaks the entire thing.
Is there a website or a youtube channel that shows some solutions to those problems?
I lokoed up a bunch of sites and videos but they never access foreign keys as values to forms(dropdowns), never make grouped dropdowns (which I made and is working without custom forms).
Small update, I'm trying with 'labels' and '|as_crispy_field' tags but "exptype" is not changing. Everything else does. and its name is matched too.
https://imgur.com/a/CvP5565
( multiple screenshots attached )
Choices needs to be a tuple like this:
[
('CHOICE_ONE', 'choice_one')
]
So, you could create the choices list like this (Lets assume TheModel has a name field.)
choices = [(i.id, i.name) for i in TheModel.objects.all()]
The second value will be displayed to the user, the first one will be set in the database.
You could use a ModelChoiceField:
class FooMultipleChoiceForm(forms.Form):
foo_select = forms.ModelMultipleChoiceField(queryset=None)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['foo_select'].queryset = ...
https://docs.djangoproject.com/en/4.1/ref/forms/fields/#modelchoicefield
In a django template, I need to use forloop.counter0 to access an element in a list. For instance:
{% for foo in bar %}
<p>{{data[{{forloop.counter0}}]}}</p>
{%endfor%}
That notation doesn't work - I guet "cannot parse the remainder: [{{forloop.....]"
So... how do I use the variable forloop.counter0 as an index for another variable (data) which is a list in a django template?
EDIT: why I think i need to do that.... Short story is that I have an inlineformset. The formset (let's call it "Discounts") is simple - a description (just a label to show users) and a % discount associated. The user only needs to enter the %. That formset is used within a Customer form. Using inlineformset_factory(Customer, Discounts, ....). There are also 2 foreign keys that must be set in that discount form (to the Customer instance being created, and to the MetaGroup, see below). But those are hidden fields and it's easy to do.
However, the number of forms I need in that formset is determined by the instances in another table (let's call it "MetaGroup"). E.g. metagroupe contains entries for stuff like say Chairs, Tables, Sofa. My "Discounts" inlineformset provides users a way to set the discount % on each of those MetaGroup.
Thus in the template I do:
...
<tbody>
<managmeent forms, error stuff here>
{% for form in formset %}
<td>{{form.field.pourcent}}</td>
That's the forloop in which I need to access the data[fooloop.counter0]. data is provided by the inclusion I use to render that table/snippet.....
EDIT: the accepted answer is solution to the narrow question - which is that I probably shouldn't do that. For the wider issue (e.g. as to why I thought I needed to do this), I ended up using what is detailed here.
Please don't. Django templates are deliberately restricted, not to do this since business logic belongs in the view, not the template.
In the view, you can zip bar and data, so then the view looks like:
def my_view(request):
bar = …
data = …
context = {
'bar_data': zip(bar, data)
}
return render(request, 'some-template.html', context)
and render this with:
{% for foo, datum in bar_data %}
<p>{{ datum }}</p>
{%endfor%}
I'm trying to duplicate the functionality of this manually created <select> using a proper Django form.
{% regroup roll_counts by get_type_display as roll_list %}
<select name="film">
<option>Select a film</option>
{% for type in roll_list %}
<optgroup label="{{ type.grouper }}">
{% for film in type.list %}
<option value="{{film.id}}">{{ film.manufacturer }} {{ film.name }} ({{ film.count }})</option>
{% endfor %}
</optgroup>
{% endfor %}
</select>
The queryset for roll_counts looks like this:
roll_counts = Film.objects\
.filter(roll__owner=owner, roll__status='storage')\
.filter(format=camera.format)\
.annotate(count=Count('name'))\
.order_by('type')
I'm trying to recreate this using some sort of Django ModelForm to be able to actually validate data submitted to the form. The ultimate goal is to be able to "load" a camera with a roll of film. Which is to say: associate a Roll (with a foreign key to Film) with a particular Camera and mark said camera with a status of loaded. So the <select> displays data that isn't being updated by the form. What needs to be modified are the Roll and Camera models, not the Film model. But I assumed all that could be handled within the view that receives data from the form.
I'm unsure how to get a ModelForm to display the aforementioned roll_count query (and nested/optgroup <select>) since it doesn't relate to a single field on that Film model.
Any ideas on how to proceed?
Update:
The accepted answer got the nesting part of the puzzle solved, but another part of it was getting the data passed from the view into the form and the QuerySet for the field working.
In the view, you can pass whatever you want to a form instance:
form = LoadCameraForm(owner=owner, format=camera.format)
In the form, I added a custom __init__ (note the owner, format between *args and **kwargs):
def __init__(self, *args, owner, format, **kwargs):
super().__init__(*args, **kwargs)
self.fields['roll_counts'].queryset = Film.objects\
.filter(roll__owner=owner, roll__status='storage')\
.filter(format=format)\
.annotate(count=Count('name'))\
.order_by('type')
(I'm still unsure how to get my annotated count to show up yet.)
Then to put all the pieces together, the field entry in the form looks like this:
roll_counts = GroupedModelChoiceField(\
label='Pick a film to load',\
queryset=None,\
group_by_field='type')
I did that once! See my snippet here :) https://djangosnippets.org/snippets/10573/
There is no built-in form field that can do what you want, so you will have to create your own custom field and a widget for rendering the field to HTML. Take a look at django.forms.fields module and you will see how the form fields are defined. Though for some, it may be a challenge to get it right.
To be honest, i would recommend to search for another option for displaying your form. You could have a select field for type and when selected, you can load the options for film using ajax?
Just my 2 cents :)
I have a app where users can register their company and then select a number of settings from a list. Both the company and services are different models.
class Company(models.Model):
name = models.CharField(max_length=100)
(...)
class Service(models.Model):
name = models.CharField(max_length=100)
linked_companies = ManyToManyField(Company, blank=True)
What I want is to have a large list of services, with checkboxes behind their names, so the owner can quickly select the services that he wants to connect to his model. This used to be done through the admin interface, but due popular demand this feature is moved to 'the front'.
The problem is that I do not know how to fit this into the traditional (generic) view/form combinations that we' ve been using so far, since two different models are involved.
I am trying a more custom solution, but have hit a wall and I am wondering if you could help me. I have created a html page that should display both the list of services and a 'save' button.
<form action="." method="POST" class="post-form">{% csrf_token %}
<ul>
{% recursetree services %}
<li>
<label><input type="checkbox" name='service' value={{ node.pk }}><h3>{{ node.name }}</h3></label>
{% if not node.is_leaf_node %}
<ul class="children">
{{ children }}
</ul>
{% endif %}
</li>
{% endrecursetree %}
</ul>
<button type="submit" class="save btn btn-default">Add Selected
</button>
</form>
I am using the following ModelForm:
class FacetForm(forms.ModelForm):
class Meta:
model = Services
fields = ['linked_tenants', 'name']
widgets = {
'linked_tenants' : CheckboxSelectMultiple()
}
This HTML page seems to work as intended, showing a long list of services with checkboxes after their names.
However, I have trouble creating a function view. Together with a collegue the following view was created
class FacetList(TenantRootedMixin, TemplateView):
def get_context_data(self, **kwargs):
d = super(ServiceList, self).get_context_data(**kwargs)
d['services'] = Services.objects.all()
d['current_company'] = self.context.company.id
return d
def form_valid(self, *args, **kwargs):
return super(ServiceList, self).form_valid(*args, **kwargs)
This view works in the sense that it shows all of the relevant information (with the checkboxes). If I change the query to filter the services by 'company id'. the view works as desired as well.
The problems I have revolve around the fact that pressing 'save'. crashes the program, throwing the following error.
'super' object has no attribute 'post'
Our program works mostly through generic classbased views and modelforms, so we have relativly limited experience with creating our own custom solutions. By my own estimation the problem seems to be twofold:
The view is probably not configured right to process the 'post' data
It is questionable if the data will be processed to the database afterwards.
Though are 'sollution' is currently flawed, are we looking in the right direction? Are we on the right way to solve our problem?
Regards
I believe you are on the right track. What I would suggest is to not be afraid to move away from generic views and move toward a more custom solution (even if you are inexperienced with it.)
The first routine that comes to my mind would be as follows:
gather all the id's that were checked by the user into a list from request.POST
Update the appropriate object's M2M field to contain these new id's.
Save the fore-mentioned object.
[Edit]
One thing I have trouble with is gathering the ID' s from the request.POST. Could you provide me with an example on how to do this?
Sure, from your HTML file I see you are creating inputs with name=service. That leads me to believe you could do something like:
ids = request.POST.get('service')
but to teach you how to fish rather than giving you a fish, you should try to simply:
print request.POST.items()
This will return and print to the console everything that was posted from your form to your view function. Use this to find out if you are getting a list of id's from the template to the server. If not, you may have to re-evaluate how you are building your form in your template.
Your first point is correct: TemplateView has no "post" method defined and that is why you get the error message when you call super().form_valid. You must either define it yourself or use a CBV which has a post method that you can override (e.g. UpdateView)
And I also believe that your second point is correct.
You would need to use an UpdateView to use the built in functionality (or CreateView).
I had a similar problem to solve (selecting values from many-to-many fields in the front-end) and I ended up with doing it "by hand" because I could not get it to work with CBV. "by-hand" => parse the values from the form, update the database, return HttpResponse
You might want to look at ModelFormSets:
https://docs.djangoproject.com/en/1.11/topics/forms/modelforms/#model-formsets
Hope this helps!
Alex
I am using formset for my project. I have several form in my formset. Now I want to customize the appearance of form. I want to do this by using the order value of each form. One example of the input for an "ORDER" of form is shown below:
<input type="text" name="phones-0-ORDER" value="1" id="id_phones-0-ORDER">
I want to get the value(value="1" in this case) of this input.
I have generated the formset from my models directly using the inlineformset_factory in my view.
https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#using-an-inline-formset-in-a-view
At the creation of my formset, I have used the following code:
PhoneNumberFormSet = inlineformset_factory(Patron, PhoneNumber, can_order=True)
In this way, every form in the formset will have an order. Let's say we have 3 forms in the formset, the first form will hold the order 1, the second order 2, the third order 3.
I want to use the "order" of the form in my template to control a loop.
Anyone knows how to get the order value in template?
For your information, the type of ORDER is IntegerField. So my question is equal to "how to get the initial(pre filled-in) data of an IntegerField in template.
Thanks for your answers!
I believe you are asking how to set initial data within a formset. If that is the case, you will find the following information valuable within the django docs:
https://docs.djangoproject.com/en/dev/topics/forms/formsets/#using-initial-data-with-a-formset
If you are actually asking about how to get to the values from the client side, you would want to do so with jquery selectors.
http://api.jquery.com/category/selectors/
{% if form.can_order %}
{{ form.ORDER }}
{% endif %}