Flask-Admin: How to prevent deletion of inline_models? - python

I'm trying to prevent the deletion of certain entries in the inline model views with flask-admin:
The respective inline model code looks like this:
class ModelCategoryValueInline(InlineFormAdmin):
form_columns = ('id', 'display_name', 'name')
form_edit_columns = ('id', 'display_name')
can_delete = False
form_args= dict(
display_name=dict(label='Display Name', validators=[DataRequired()]),
name=dict(label='Technical Name', validators=[DataRequired()]),
)
def on_model_delete(self, model):
# Not called at all..
print('on_model_delete', model)
if model.builtin == True:
raise ValidationError(f'[{model}] is a build-in CategoryValue and cannot be deleted.')
def on_model_change(self, form, model, is_created):
# Is called, but has model already changed... - deleted models do not get this event
if not is_created:
if form.form.name.data != model.name:
raise ValidationError(f'You cannot change the internal name of a category value!')
def on_form_prefill(self, form, id):
# not called att all
form.name.render_kw = {'disabled': 'disabled', 'readonly': True}
I haven't figured out a way to prevent deletion of inline entries. on_model_delete() is not called when deleting the inline entries. can_delete has no effect either.
How can I disable the deletion of inline entries?
Ideally I want to be able to control deletion via the on_model_delete() method and prevent only deletion of values that match certain criteria.

Even though this is not an ideal solution I've managed to achieve the desired behaviour by editing the inline_list_base.html template:
{% macro render_inline_fields(field, template, render, check=None) %}
<div class="inline-field" id="{{ field.id }}">
{# existing inline form fields #}
<div class="inline-field-list">
{% for subfield in field %}
<div id="{{ subfield.id }}" class="inline-field well well-sm">
{%- if not check or check(subfield) %}
<legend>
<small>
{{ field.label.text }} #{{ loop.index }}
<div class="pull-right">
{% if subfield.get_pk and subfield.get_pk() %}
# Added if statement to check if field is builtin and disable checkbox
{% if subfield.object_data.builtin %}
<label for="del-{{ subfield.id }}" style="display: inline"><i title="This is a built-in value and cannot be deleted" data-toggle="tooltip">Built-in</i></label>
{% else %}
<input type="checkbox" name="del-{{ subfield.id }}" id="del-{{ subfield.id }}" />
<label for="del-{{ subfield.id }}" style="display: inline">{{ _gettext('Delete?') }}</label>
{% endif %}
{% else %}
<i class="fa fa-times glyphicon glyphicon-remove"></i>
{% endif %}
</div>
</small>
</legend>
<div class='clearfix'></div>
{%- endif -%}
{{ render(subfield) }}
</div>
{% endfor %}
</div>
{# template for new inline form fields #}
<div class="inline-field-template hide">
{% filter forceescape %}
<div class="inline-field well well-sm">
<legend>
<small>{{ _gettext('New') }} {{ field.label.text }}</small>
<div class="pull-right">
<span class="fa fa-times glyphicon glyphicon-remove"></span>
</div>
</legend>
<div class='clearfix'></div>
{{ render(template) }}
</div>
{% endfilter %}
</div>
<a id="{{ field.id }}-button" href="javascript:void(0)" class="btn btn-default" onclick="faForm.addInlineField(this, '{{ field.id }}');">{{ _gettext('Add') }} {{ field.label.text }}</a>
</div>
{% endmacro %}
When placing this file in templates/admin/model/inline_list_base.html this has the desired effect and prevents the user from deleting certain fields:
I would want to have a check also at the backend but I haven't found a solution for that yet.

My solution was to use something similar to
class NoDeleteInlineModelFormList(InlineModelFormList):
def display_row_controls(self, field):
return False
class NoDeleteInlineModelConverter(InlineModelConverter):
inline_field_list_type = NoDeleteInlineModelFormList
class ParentView(sqla.ModelView):
inline_models = [Child]
inline_model_form_converter = NoDeleteInlineModelConverter
You can find more details at my repo https://github.com/mapio/Flask-Admin-Inline-Models-And-Related-Fields

Related

Django form fields not rendering when grouping fields

I am using Django 4.0.3 with a bootstrap webinterface.
For layout reasons I want my fields in a ModelForm to be grouped and I'm doing it with:
class UserForm(ModelForm):
template_name = "container/form.html"
field_groups = [["email", "company"], ["last_name", "first_name"]]
grouped_fields = []
class Meta:
model = MyUser
fields = ["email", "company", "first_name", "last_name"]
__init__(self, *args, **kwargs):
super(UserForm, self).__init__(*args, **kwargs)
def group_fields(self):
for group in self._field_groups:
group_entry = []
for entry in group:
group_entry.append(self.fields[entry])
self.grouped_fields.append(group_entry)
in the view I initialize my form and the regroup the fields:
def user_form(request):
form = UserForm()
form.group_fields()
render(request, "page.html, {"form", form})
The page.html looks like this:
<body>
<div id="form-wrapper">
{{ form }}
</div>
</body>
and the form.html looks like this:
<form action="" method="POST">{% csrf_token %}
{% for field_group in form.grouped_fields %}
<div class="row">
{% for field in field_group %}
<div class="col">
<label>{{ field.label }}</label> {{ field }}
</div>
{% endfor %}
</div>
{% endfor %}
However the rendered fields are displayed as string representations of the field objects:
Email <django.forms.fields.EmailField object at 0x7f98c00e03a0>
Company <django.forms.fields.CharField object at 0x7f98c00e0250>
Last name <django.forms.fields.CharField object at 0x7f98c00e0790>
First name <django.forms.fields.CharField object at 0x7f98c00e10c0>
Whereas a common call renders as expected a form with input fields, but not with the desired layout.
{% for field in form %}
<div class="row">
<div class="col">
{{ field.label_tag }} {{ field }}
</div>
</div>
{% endfor %}
I also tried to move the call of the group_fields method to the form init but had no success.
Is there a way to render the fields, that are stored in my grouped_fields container correctly?
After quiet a time of trying this and that I ended up writing a wrapper for ModelForm, define my field_groups there and rendered it in the template.
It is not nice, but it does the trick.
<form action="" method="post">
{% for fieldgroup in form.field_groups %}
<div class="row fieldgroup">
{% for fieldname in fieldgroup %}
{% for field in form %}
{% if field.name == fieldname %}
<div class="col pt-2">
<label class="form-label">{{ field.label }}</label>
{{ field }}
</div>
{% endif %}
{% endfor %}
{% endfor %}
</div>
{% endfor %}
<button type="submit">Submit</button>
</form>

django formset template loop through fields disables delete featuer

Thank you for being here.
I am not able to delete objects in django formset While i am looping through the fields in templates the.
I can see the deletion box but when i hit submit the page refreshes and the object is exist.
template.html
{% for form in request_equipment_form %}
<div class="card" style="width: 100% ">
<div class="card-body">
<div class="d-flex flex-row" style="width:100%">
{{form.equipment}}
{{form.quantity}}
{{form.DELETE}}
{{form.ORDER}}
</div>
</div>
{% endfor %}
But when i do not loop through the fields the delete feature works like charm
{% for form in request_equipment_form %}
<div class="card" style="width: 100% ">
<div class="card-body">
<div class="d-flex flex-row" style="width:100%">
{{form.as_p}}
</div>
</div>
{% endfor %}
views.py
if formset.is_valid():
instances = formset.save(commit=False)
for d_obj in formset.deleted_objects:
d_obj.delete()
if not instances:
return redirect(request.META.get('HTTP_REFERER'))
for instance in instances:
instance.user = request.user
instance.flight = flight
instance.station = flight.station
instances = formset.save(commit=False)
for instance in instances:
instance.save()
print(instance.date)
return redirect(request.META.get('HTTP_REFERER'))
When you loop through forms in a formset you need to include the the management form for things to work:
<form method="post">
{{ formset.management_form }}
{% for form in formset %}
{% for field in form %}
{{ field.label_tag }} {{ field }}
{% endfor %}
{% endfor %}
</form>
Also, you should consider using the CSRF token ({% csrf_token %}) to secure your form.

How to display the field "label" in django's builtin password validation errors?

I am using Django's builtin authentication class views and need to customize the error message displayed when password validation fails.
For example, in the builtin PasswordResetView, if I try to change my password to test, the following errors will display in my template:
new_password2
This password is too short. It must contain at least 8 characters.
This password is too common.
I would like to change new_password2 to New Password.
Here is the relevant part of my template for the PasswordResetView:
{% extends 'registration/base.html' %}
{% block card_body %}
<div class="form-group">
<label for="old_password">
Old Password:
{{ form.old_password }}
</label>
</div>
<div class="form-group">
<label for="new_password1">
New Password:
{{ form.new_password1 }}
</label>
</div>
<div class="form-group">
<label for="new_password2">
Confirm Password:
{{ form.new_password2 }}
</label>
</div>
<div class="form-group">
<input type="submit" value="Change" class="btn float-right login_btn">
</div>
{% endblock card_body %}
{% block card_footer %}
{% if form.errors %}
<p class="d-flex justify-content-center links">
{{ form.errors }}
</p>
{% endif %}
{% endblock card_footer %}
Supply some dict to the template that will map form field names to labels you want like:
fields_mapping = {
'old_password': 'Old password',
'new_password1': 'New password',
'new_password2': 'Confirm password'
}
Just manually iterate over errors and use the the mapping dic to convert field names to labels you want:
{% for field_name in form.errors: %}
{{ fields_mapping[field_name] }}:
{% for err_message in form.errors[field_name]: %}
* {{ err_message }}
{% endfor %}
{% endfor %}
Customize HTML/CSS there as you want

Django Forms: styling forms (checkboxes) manually - Input to be outside of label

I need to render checkbox form fields in the following format in html template:
<input id="tag" type="checkbox" name="check" value="1">
<label for="tag">Tag 1</label>
Currently, in my template I tried:
{{ filter.form.tags.errors }}
{% for field in filter.form.tags %}
<input id="{{ field.id_for_label }}" type="checkbox" name="check">
<label for="{{ field.id_for_label }}">{{ field.value }}</label>
{% endfor %}
This will not render {{ field.value }} (I get empty instead of Tag 1) and also is non sticky.
I also tried:
{% for field in filter.form.tags %}
{{ field.label_tag }}
{{ field }}
{% endfor %}
Which gives me nested of <label><input></input></label>, but displays everything that I need.
Is it possible to render a form such that I get the format that I'm after? (It should also preserve checked e.g. make ticks sticky).
Edit:
class TaskFilter(django_filters.FilterSet):
"""Filter for books by author"""
tags = django_filters.ModelMultipleChoiceFilter(widget=forms.CheckboxSelectMultiple, queryset=Task.tags.most_common())
title = django_filters.CharFilter(field_name='title', lookup_expr='icontains')
class Meta:
model = Task
fields = ['tags', 'title']
views.py
def task_list_filter(request):
if request.method == 'GET':
form = AdditionalForm(request.GET)
# process the form
results = Task.objects.all().order_by('-created')
f = TaskFilter(request.GET, queryset=results)
# Output final query set
results = f.qs[0:100] # only get first 100 objects
# Create pagination
# Get the view
return render(request, 'base/task_list_filter.html', {'filter': f,
'task_list': results,
'form': form })
To make sure the checkbox is in the correct state you can use the example you yourself provided with a few changes:
{% for field in filter.form.tags %}
<input type="checkbox" id="{{ field.id_for_label }}"
name="{{ field.html_name }}"
{% if field.value %}checked{% endif %}>
<label for="{{ field.id_for_label }}">{{ field.value }}</label>
{% endfor %}
And remember that you can use the {{ field.label_tag }} to generate the entire label HTML tag.
In HTML
{% for field in filter.form.tags %}
{{ field.tag }} <!-- Generates the checkbox for the field -->
{{ field.choice_label }} <!-- Generates the named label of the field -->
{% endfor %}
I learned that from this guy who has lots of helpful information specific to using Django.

Create a custom html template using forms in Django

I've been going over the docs and some StackOverflow examples but I'm still having a little trouble. In Django I created a form, the view renders it and I have it displayed as form.as_p in the HTML template. This works perfectly, but I would like to be able to customize my HTML template instead of having it displayed as form.as_p. Any examples on how to do so?
So far I have the following.
View:
#login_required
def register(request):
info = Content.objects.get(id=request.user.id)
if request.method == 'POST':
form = UploadFileForm(request.POST, request.FILES, instance=info)
if form.is_valid():
info = form.save(commit=False)
info.save()
return HttpResponseRedirect('/portal/register')
else:
form = UploadFileForm(instance=info)
return render(request, 'portal/register.html', {'form': form, 'gallery': info})
Form:
class UploadFileForm(ModelForm):
logo = forms.ImageField(required=False)
image1 = forms.ImageField(required=False)
terms = forms.CharField(required=False)
class Meta:
model = Content
fields = ['user', 'logo', 'image1', 'terms']
Model:
class Content(models.Model):
user = models.ForeignKey(User)
logo = models.ImageField(upload_to=content_file_name, null=True, blank=True)
image1 = models.ImageField(upload_to=content_file_name, null=True, blank=True)
terms = models.CharField(max_length="256", blank=True)
def __unicode__(self):
return self.title
HTML Template:
<form method="POST" action="" enctype="multipart/form-data">
{% csrf_token %}
<div class="col-lg-3 col-md-4 col-xs-6 thumb">
<a class="thumbnail" href="#">
{% if gallery.logo %}
<img class="img-responsive" id="logo" src="/media/{{ gallery.logo }}" alt="">
{% else %}
<img class="img-responsive" id="logo" src="/media/images/placeholder.png" alt="">
{% endif %}
</a>
<input type="file" name="logo" id="logo" multiple>
</div>
<div class="col-lg-3 col-md-4 col-xs-6 thumb">
<a class="thumbnail" href="#">
{% if gallery.image1 %}
<img class="img-responsive" id="image1" src="/media/{{ gallery.image1 }}" alt="">
{% else %}
<img class="img-responsive" id="image1" src="/media/images/placeholder.png" alt="">
{% endif %}
</a>
<input type="file" name="image1" id="image1" multiple>
</div>
<div class="form-group">
<input type="text" name="terms" id="terms" class="form-control input-sm" placeholder="terms" value="{{ gallery.terms }}">
</div>
<input type="submit" value="Submit" />
</form>
Yes you can loop on the form fields by doing the following
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
<label>
{{ field.label_tag }}
</label>
<div>
{{ field }}
</div>
</div>
{% endfor %}
Then you can add class to the div and label tags and style the form fields
You can also use Django Widget Tweaks to add classed to the form fields.
Click here to read in more details
By using widget tweaks you simply find the field in which you want to add the class by doing this
{% load widget_tweaks %}
{{ form.name|add_class:"inputContact volunteer" }}
You can render the fields manually:
We don’t have to let Django unpack the form’s fields; we can do it
manually if we like (allowing us to reorder the fields, for example).
Each field is available as an attribute of the form using {{
form.name_of_field }}, and in a Django template, will be rendered
appropriately.
Example from the documentation:
{{ form.non_field_errors }}
<div class="fieldWrapper">
{{ form.subject.errors }}
<label for="{{ form.subject.id_for_label }}">Email subject:</label>
{{ form.subject }}
</div>
<div class="fieldWrapper">
{{ form.message.errors }}
<label for="{{ form.message.id_for_label }}">Your message:</label>
{{ form.message }}
</div>
As I said in the comments, see also Django Crispy Forms. You can achieve the same without so much markup.

Categories

Resources