So I have a model called Users and it has a field called first_name.
class Users(models.Model):
alpha_field = RegexValidator(regex=r'^[a-zA-Z]+$', message='Name can only contain letters')
user_id = models.AutoField(unique=True, primary_key=True)
username = models.SlugField(max_length=50, unique=True)
first_name = models.CharField(max_length=50, verbose_name='first Name', validators=[alpha_field])
last_name = models.CharField(max_length=50, validators=[alpha_field])
password = models.SlugField(max_length=50)
and then I created a UsersForm and then in my template page, when displaying any error messages, it doesn't use the verbose name, it uses first_name. For example, my template code for display errors is
{% for field, error in form.errors.items %}
{% if forloop.counter == 1 %}
{{ field }}{{ error | striptags }}
{% endif %}
{% endfor %}
If there is an error in the first_name field, like if I didn't fill it out and still clicked submit, it would display this
"first_nameThis field is required"
How do I make it display
"First NameThis field is required" instead?
Not that it might make a different but do note that I am using south and schemamigration to update the database, it originally did not have a verbose name but I recently added it and then just saved the file (I didn't do a schemamigration and then migrate the app because it said that no changes seem to have been made).
My UsersForm is this:
from django import forms
from models import Users
class UsersForm(forms.ModelForm):
class Meta:
model = Users
widgets = {'password':forms.PasswordInput()}
def __init__(self, *args, **kwargs):
super( UsersForm, self ).__init__(*args, **kwargs)
self.fields[ 'username' ].widget.attrs[ 'placeholder' ]="Username"
self.fields[ 'first_name' ].widget.attrs[ 'placeholder' ]="First Name"
self.fields[ 'last_name' ].widget.attrs[ 'placeholder' ]="Last Name"
self.fields[ 'password' ].widget.attrs[ 'placeholder' ]="Password"
self.fields['first_name'].label='first Name'
my view is here:
def home_page(request):
form = UsersForm()
if request.method == "POST":
form = UsersForm(request.POST)
if form.is_valid():
form.save()
c = {}
c.update(csrf(request))
c.update({'form':form})
return render_to_response('home_page.html', c)
form.errors is a dictionary of field NAMES as the keys and error messages as the values. It will not be the verbose_name. You need to get the field from the form, then do field.label for the verbose_name. If you use this snippet for getting an attribute on an object dynamically in a template: https://snipt.net/Fotinakis/django-template-tag-for-dynamic-attribute-lookups/, you can do something like this to get the verbose_name:
{% load getattribute %}
{% for field, error in form.errors.items %}
{% if forloop.counter == 1 %}
{% with field_obj=form|getattribute:field %}
{{ field_obj.label }}{{ error | striptags }}
{% endwith %}
{% endif %}
{% endfor %}
I had similar problem some time ago, this snippet helped me out:
from django import template
from django import forms
from django.forms.forms import NON_FIELD_ERRORS
from django.forms.util import ErrorDict
register = template.Library()
#register.filter
def nice_errors(form, non_field_msg='General form errors'):
nice_errors = ErrorDict()
if isinstance(form, forms.BaseForm):
for field, errors in form.errors.items():
if field == NON_FIELD_ERRORS:
key = non_field_msg
else:
key = form.fields[field].label
nice_errors[key] = errors
return nice_errors
http://djangosnippets.org/snippets/1764/
Related
I am making a django project and I have a form for the User to add a Vehicle Manually that will be assigned to him. I also would like to had an option for the user to choose a vehicle based on the entries already present in the database.
vehicles/models.py
class Vehicle(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
nickname = models.CharField(unique = True, max_length=150)
date_joined = models.DateTimeField(default=timezone.now)
brand = models.CharField(max_length=150)
battery = models.CharField(max_length=150)
model = models.CharField(max_length=150)
def __str__(self):
return self.nickname
def get_absolute_url(self):
return reverse('vehicle-list')
class Meta:
db_table = "vehicles"
I created a form so the user can add his Vehicles as such:
vehicles/forms.py
class VehicleAddFormManual(forms.ModelForm):
class Meta:
model = Vehicle
fields = ('brand','model', 'battery', 'nickname')
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
self.fields['brand']
self.fields['model']
self.fields['battery']
self.fields['nickname']
The corresponding view:
vehicles/views.py
class AddVehicleViewManual(LoginRequiredMixin, CreateView):
model = Vehicle
form_class = VehicleAddFormManual
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
The html file:
vehicles/templates/vehicles/vehicle_form.html
{% extends "blog/base.html" %}
{% block content %}
{% load crispy_forms_tags %}
<div class="content-section">
<form method="POST">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">New Vehicle</legend>
{{ form|crispy }}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Submit</button>
</div>
</form>
</div>
{% endblock content %}
I would like to add another form in which the user has a dropdown with option with the brands, models and batteries that already exist in the database. If there's a car in the database with brand: Tesla, model: Model 3, battery: 50 kWh, then it would appear in the dropbox as a choice for each field.
I'm not sure how to do this and sorry for the newbie question... Thanks in advance!
I once had to do something similar, but I needed a form which had one checkbox for each item in a list of externally-supplied strings. I don't know if this is the cleanest way, but I used python metaclasses:
class SockSelectForm(forms.Form):
#staticmethod
def build(sock_names):
fields = {'sock_%s' % urllib.parse.quote(name):
forms.BooleanField(label=name, required=False)
for name in sock_names}
sub_class = type('DynamicSockSelectForm', (SockSelectForm,), fields)
return sub_class()
In my get() method, I instantiate it as:
form = SockSelectForm.build(names)
and the corresponding form handling in the post() method is:
form = SockSelectForm(request.POST)
I suspect if you look under the covers of Django's ModelForm, you'd see something similar, but I couldn't use ModelForm because it's too closely tied to the model system for what I needed to do.
model.py
class DropdownModel(models.Model):
brand = models.CharField(max_length=150)
battery = models.CharField(max_length=150)
model = models.CharField(max_length=150)
def __str__(self):
return self.brand.
form.py
from .models import DropdownModel
all_brand = DropdownModel.objects.values_list('brand','brand')
all_battery = DropdownModel.objects.values_list('battery','battery')
all_model= DropdownModel.objects.values_list('model','model')
class DropdownForm(forms.ModelForm):
class Meta:
model = DropdownModel
fields = "__all__"
widgets = {
'brand':forms.Select(choices=all_brand),
'battery':forms.Select(choices=all_battery),
'model':forms.Select(choices=all_model),
}
view.py
from django.shortcuts import render
from .form import DropdownForm
# Create your views here.
def HomeView(request):
form = DropdownForm()
context = {'form':form}
return render(request,'index.html',context)
index.html
{% extends "base.html" %}
{% load static %}
{% block title %}
Index | Page
{% endblock title %}
{% block body %}
{{form.as_p}}
{% endblock body %}
Output-
Note- if u can't see updated values in dropdown do server restart because localhost not suport auto update value fill in dropdown it's supoorted on live server
Thank you
I have a problem validating django formsets when i build several formsets dynamically
In this case the one client could be various brands and contact people.
models.py
class Client(ChangesMixin, models.Model):
name = models.CharField(verbose_name="Nombre", max_length=100, unique=True)
code = models.PositiveIntegerField(verbose_name="Código", blank=True)
class Meta:
verbose_name = "Cliente"
verbose_name_plural = "Clientes"
class Brand(ChangesMixin, models.Model):
name = models.CharField(verbose_name="Marca", max_length=100, blank=True, null=True)
client = models.ForeignKey('Client', verbose_name="Cliente", related_name='brand_client', on_delete=models.DO_NOTHING)
class Meta:
verbose_name = "Marca"
verbose_name_plural = "Marcas"
class Contact(ChangesMixin, models.Model):
name = models.CharField(verbose_name="Contacto", max_length=100, blank=True, null=True)
client = models.ForeignKey('Client', verbose_name="Cliente", related_name='contact_client', on_delete=models.DO_NOTHING)
class Meta:
verbose_name = "Contacto"
verbose_name_plural = "Contactos"
I have two method to create forms and formsets dynamically
forms.py
def get_custom_formset(entry_model=None, entry_fields=None, action=None):
formset = None
if action == 'create':
extra = 1
else:
extra = 0
formset = modelformset_factory(
model = entry_model,
extra = extra,
form = get_custom_form(entry_model, entry_fields, action)
return formset
def get_custom_form(entry_model=None, entry_fields=None, action=None):
class _CustomForm(forms.ModelForm):
class Meta:
model = entry_model
fields = [field.name for field in entry_fields]
def __init__(self, *args, **kwargs):
"""
"""
super(_CustomForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.pk:
for field in entry_fields:
if action == 'detail':
self.fields[field.name].widget.attrs['readonly'] = True
return _CustomForm
I have a creation class view with get and post methods, depends on model passed.
I get the model fields to build the form and if a field is a foreign key i build formsets with these concrete model.
views.py
class CustomCreateView(LoginRequiredMixin, View, PermissionRequiredMixin):
model = None
template = 'create.html'
def get(self, request, *args, **kwargs):
template_form = str(self.model._meta.verbose_name).lower() + "_form.html"
model_fields = self.model._meta.get_fields()
form = None
formset = None
formsets = {}
for main_field in model_fields:
main_field_name = main_field.__class__.__name__
if main_field_name == 'ManyToOneRel':
model_names = str(main_field.name).split("_")
submodel = apps.get_model('app', model_names[0])
submodel_fields = submodel._meta.get_fields()
formset = app_forms.get_custom_formset(submodel, submodel_fields, 'create')
queryset = submodel.objects.none()
UPDATED with SOLUTION
formset = formset(queryset=queryset, prefix=submodel.__name__.lower())
formsets[submodel._meta.verbose_name.lower()] = formset
elif main_field_name == 'ManyToManyField':
print("NOT PROVIDED YET")
form = app_forms.get_custom_form(self.model, model_fields, 'create')
form = form(prefix=self.model.__name__.lower())
return render(request, self.template, {
'form': form,
'formsets': formsets,
'template_form': template_form,
})
def post(self, request, *args, **kwargs):
template_form = str(self.model._meta.verbose_name).lower() + "_form.html"
model_fields = self.model._meta.get_fields()
for main_field in model_fields:
main_field_name = main_field.__class__.__name__
if main_field_name == 'ManyToOneRel':
model_names = str(main_field.name).split("_")
submodel = apps.get_model('app', model_names[0])
submodel_fields = submodel._meta.get_fields()
formset = app_forms.get_custom_formset(submodel, submodel_fields, 'create')
queryset = submodel.objects.none()
formset = formset(queryset=queryset, prefix=submodel.__name__.lower())
formsets[submodel.__name__.lower()] = formset
elif main_field_name == 'ManyToManyField':
print("NOT PROVIDED YET")
form = app_forms.get_custom_form(self.model, model_fields, 'create')
form = form(request.POST, prefix=self.model.__name__.lower())
for prefix, formset in formsets.items():
formset = formset.__class__(request_post, prefix=prefix)
if formset.is_valid() and form.is_valid(): HERE THROWS THE ERROR
For templates i have 3 levels to build forms and formsets dynamically
create.html
{% get_url urls 'create' as element_create %}
<form class="" action="{% url element_create %}" method="POST">
{% csrf_token %}
{% include template_form %}
{% if formsets|length > 0 %}
{% for subtemplateformname, formset in formsets.items %}
{% include 'formset.html' %}
{% endfor %}
{% endif %}
</form>
formset.html
{{ formset.management_form }}
{% for form in formset %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% include 'form.html' %}
{% endfor %}
form.html
{% load widget_tweaks %}
{% for field in form %}
<div class="form-group{% if field.errors %} has-error{% endif %}">
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
{{ field|add_class:"form-control" }}
{% for error in field.errors %}
<p class="help-block">{{ error }}</p>
{% endfor %}
</div>
{% endfor %}
Firstly, when you get a formset for a related model you pass sub_fields but I have no idea where this is coming from?
formset = app_forms.get_custom_formset(submodel, sub_fields, 'create')
The error is with the way you are defining the formset prefix. In the GET you are passing model_names[0], which for a normal relationship will be the model name lowercase without any spaces. Lets use a model named MyModel for example
main_field.name # 'mymodel_set'
model_names = str(main_field.name).split("_") # ['mymodel', 'set']
model_names[0] # 'mymodel'
formset = formset(queryset=queryset, prefix=model_names[0])
When you assign the formset to the formsets dictionary you are using something different even though you are treating it the same
formsets[submodel._meta.verbose_name.lower()] = formset
...
for prefix, formset in formsets.items():
formset = formset.__class__(request_post, prefix=prefix)
submodel._meta.verbose_name will return the model name with spaces. So any models that have 2 "words" in it's verbose name will not set the correct prefix (e.g. MyModel._meta.verbose_name.lower() == 'my model' but model_names[0] == 'mymodel')
I have created a OnetoOne model for some user preference checkboxes. This is mapped to the User, and using signals I create it when the user is created. Here is what I have so far:
Model:
class DateRegexes(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
prefix_1 = models.NullBooleanField()
prefix_2 = models.NullBooleanField()
prefix_3 = models.NullBooleanField()
prefix_4 = models.NullBooleanField()
prefix_5 = models.NullBooleanField()
prefix_6 = models.NullBooleanField()
prefix_7 = models.NullBooleanField()
prefix_8 = models.NullBooleanField()
prefix_9 = models.NullBooleanField()
#receiver(post_save, sender=User)
def create_date_regexes(sender, instance, created, **kwargs):
if created:
DateRegexes.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_date_regexes(sender, instance, **kwargs):
instance.date_prefs.save()
Form:
class DatePreferenceForm(forms.ModelForm):
class Meta:
model = DateRegexes`
View:
#login_required
def set_date_preferences(request):
if request.method == 'POST':
form = DatePreferenceForm(request.POST)
if form.is_valid():
form.save()
else:
date_prefs = get_object_or_404(DateRegexes, user=request.user)
form = DatePreferenceForm(instance = request.user.date_prefs)
return render(request, 'set_date.html', {'form': form})
Template:
{% extends 'base.html' %}
{% block title %}Date Preferences{% endblock %}
{% block content %}
{% if user.is_authenticated %}
<p>logout</p>
<form method="post">
{% csrf_token %}
{% for field in form.visible_fields %}
<div>here {{ field }}</div>
{% endfor %}
<button type="submit">Set</button>
</form>
</div>
{% else %}
<p>You are not logged in</p>
login
{% endif %}
{% endblock %}
When I execute this, the first thing that happens is that the expected checkboxes that are filled in according to their previously selected preferences, I get a bunch of dropboxes that each say:
"Unknown", "Yes", "No".
1) How can I get it show html checkboxes instead of the dropboxes?
2) When I submit the form I get:
IntegrityError at /db/set_date_preferences/
NOT NULL constraint failed: db_dateregexes.user_id
Which I understand that it is having trouble associating my form to the logged in user, but I'm not sure what the right way to associate this should be
You're using a NullBooleanField which can have 3 values: None, True or False. So the default widget to render such a field is a dropdown. If you want a checkbox you should decide what you want None to map to and override the fields in the DatePreferenceForm to be a BooleanField.
Second, since every user already has prefs you should initialise your form with the correct instance, even in the POST case. I don't really understand how your form can be valid since user is a required field (you should really exclude it from the form).
class DatePreferenceForm(forms.ModelForm):
class Meta:
model = DateRegexes
exclude = ['user']
# View
def set_date_preferences(request):
if request.method == 'POST':
form = DatePreferenceForm(request.POST, instance=request.user.date_prefs)
if form.is_valid():
form.save()
# would be good practice to redirect to a success page here
else:
date_prefs = get_object_or_404(DateRegexes, user=request.user)
form = DatePreferenceForm(instance = request.user.date_prefs)
return render(request, 'set_date.html', {'form': form})
i have views.py like this
class InventoryListView(ListView):
context_object_name = 'inventorys'
model = models.Inventory
def get_context_data(self, **kwargs):
context = super(InventoryListView, self).get_context_data(**kwargs)
context['form'] = InventoryForm()
return context
class InventoryCreateView(CreateView):
fields = ('name', 'sn', 'desc', 'employee')
model = models.Inventory
and here my models.py
class Inventory(models.Model):
name = models.CharField(max_length=255)
sn = models.DecimalField(max_digits=20, decimal_places=0)
desc = models.TextField(blank=True, null=True, default='Empty')
employee = models.ForeignKey(Employee, null=True, on_delete=models.SET_NULL, related_name='employee')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True, null=True)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse("system:inventory_list")
and here my urls.py
url(r'^invn/$',views.InventoryListView.as_view(), name='inventory_list'),
url(r'^invn/create$',views.InventoryCreateView.as_view(), name='inventory_create'),
and here my inventory_list.html
{% for inventory in inventorys %}
<td>{{ inventory.name }}</td>
{% endfor %}
<form method="post" action="{% url 'system:inventory_create' %}">
{% csrf_token %}
{{ form.as_p }}
</form>
if the form is valid, its work well.
But if the form is invalid it will redirect to inventory_form.html that will cause TemplateDoesNotExist since Django CreateView need inventory_form.html (CMIIW).
simple solution create inventory_form.html but that not what I want.
how to make it redirect to my ListView when the form is invalid with error messages ?...
It's easy to redirect to ListView.
Just override form_invalid() in CreateView.
Default form_invalid() is below
def form_invalid(self, form):
"""If the form is invalid, render the invalid form."""
return self.render_to_response(self.get_context_data(form=form))
You can redirect to your ListView.
But the problem is, when redirect, you cannot pass any context or message to them. It just redirects to your listview.
So you have few choices.
First, you can keep form_invalid() the same, and if form is invalid (you can check it with error with form.errors) show alert error message in the CreateView page with alert or confirm, and when click ok, redirect to listview page.
Second, you can use session. Before redirect to listview, you can give some session data for deciding it's from invalid CreateView.
I think first one is better approach for web.
If you have any question, just comment it.
Possible to redirect User inside form_invalid() method, and use the Django Message Framework, to alert the user (Error Message).
from django.contrib import messages
from django.shortcuts import redirect
class InventoryCreateView(CreateView):
fields = ('name', 'sn', 'desc', 'employee')
model = models.Inventory
def form_invalid(self, form):
'form is invalid'
messages.add_message(self.request,messages.WARNING,"Form is invalid")
return redirect('inventory_list')
To retrieve the message in template:
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
So I want to use this getattribute function (found on this link) https://snipt.net/Fotinakis/django-template-tag-for-dynamic-attribute-lookups/
in my django templates. I created a templatetags folder in my app folder where my models.py is. I also created and saved an empty inint.py file in the templatetags folder. I then created a file called getattribute.py in the template tags folder and copy-pasted the snippet found in the link above into the getattribute.py file and saved the file.
This is what my template looks like:
<html>
<body>
<form method="post" action="">{% csrf_token %}
{{ form.first_name }} {{form.last_name }} <br>
{{ form.username }} {{ form.password }} <br>
<input type="submit" value="Register"/>
</form>
{% load getattribute %}
{% for field, error in form.errors.items %}
{% if forloop.counter == 1 %}
{% with field_obj=form|getattribute:field %}
{{ field_obj.label }}{{ error | striptags }}
{% endwith %}
{% endif %}
{% endfor %}
</body>
</html>
This is how my models.py looks.
class Users(models.Model):
alpha_field = RegexValidator(regex=r'^[a-zA-Z]+$', message='Name can only contain letters')
user_id = models.AutoField(unique=True, primary_key=True)
username = models.SlugField(max_length=50, unique=True)
first_name = models.CharField(max_length=50, verbose_name='first Name', validators=[alpha_field])
last_name = models.CharField(max_length=50, validators=[alpha_field])
password = models.SlugField(max_length=50)
My forms.py is this.
class UsersForm(forms.ModelForm):
class Meta:
model = Users
widgets = {'password':forms.PasswordInput()}
def __init__(self, *args, **kwargs):
super( UsersForm, self ).__init__(*args, **kwargs)
self.fields[ 'username' ].widget.attrs[ 'placeholder' ]="Username"
self.fields[ 'first_name' ].widget.attrs[ 'placeholder' ]="First Name"
self.fields[ 'last_name' ].widget.attrs[ 'placeholder' ]="Last Name"
self.fields[ 'password' ].widget.attrs[ 'placeholder' ]="Password"
self.fields['first_name'].label='first Name'
and this is my views.py
def home_page(request):
form = UsersForm()
if request.method == "POST":
form = UsersForm(request.POST)
if form.is_valid():
form.save()
c = {}
c.update(csrf(request))
c.update({'form':form})
return render_to_response('home_page.html', c)
Now, when I run the server, the form is displayed with no errors. However, if I purposely not fill out the first name section and hit submit, it says "This field is required."
I want it to say the verbose name and the snippet is supposed to make it say the verbose name if, in my template, I use {{ field_obj.label }}, right? But it is not display the verbose name for some reason. I'm guessing it is because I'm not using the templatetags properly?
In your form, it may help to have a clean method:
def clean(self):
first_name = self.cleaned_data.get('first_name')
if password is None:
raise forms.ValidationError('This is a custom error message.')
return self.cleaned_data
Then, in your template, you can have code like:
{{ form.first_name }}
{% if form.first_name.errors %}
{{form.first_name.errors.as_text}}
{% endif %}
Otherwise, django's default form validation will treat all your inputs as required, and provide that generic message.
Another option, from the docs, is to define error messages at the form field level: https://docs.djangoproject.com/en/dev/ref/forms/fields/
name = forms.CharField(error_messages={'required': 'Please enter your name'})
This will add the custom error message based on the error type, so in your case, you could use something like:
first_name = models.CharField(max_length=50, verbose_name='first Name', validators=[alpha_field], error_messages={'required': 'Please enter a first name'})