I need to validate a condition in my Experiment class that depends on more than one field. I specified a 'clean' method in my ExperimentForm, but the validation method is never raised. The model's validation errors are perfectly displayed.
This is how the forms.py looks like:
from django import forms
from django.utils.translation import ugettext, ugettext_lazy as _
class ExperimentForm(forms.Form):
name = forms.CharField(max_length=254)
student_n = forms.IntegerField()
expert_n = forms.IntegerField()
student_cmd_n = forms.IntegerField()
expert_cmd_n = forms.IntegerField()
is_active = forms.BooleanField()
error_messages = {
'insufficient_assignments': _("The total number of commands to be evaluated must be"
"Greater than 28. Right now it's %(command_number)"),
}
def clean(self):
cleaned_data = super(ExperimentForm, self).clean()
s_n = cleaned_data.get("student_n")
e_n = cleaned_data.get("expert_n")
s_cmd_n = cleaned_data.get("student_cmd_n")
e_cmd_n = cleaned_data.get("expert_cmd_n")
command_number = s_n*s_cmd_n + e_n*e_cmd_n
if command_number < 28:
raise forms.ValidationError(
self.error_messages['insufficient_assignments'],
code='insufficient_assignments',
params={'command_number': command_number},
)
return self.cleaned_data
This is my views.py
from __future__ import unicode_literals
from django.shortcuts import render
from django.http import HttpResponse
from django.template import loader
from django.core.urlresolvers import reverse_lazy
from vacs.forms import ExperimentForm
from django.views.generic import TemplateView,ListView
from django.views.generic.edit import FormView, CreateView, UpdateView, DeleteView
from django.views.generic.detail import DetailView
from vacs.models import Experiment
class ExperimentView(FormView):
template_name = 'vacs/experiment.html'
form_class = ExperimentForm
success_url = '/vacs/experiments/'
def form_valid(self, form):
return super(ExperimentView, self).form_valid(form)
class ExperimentDetailView(DetailView):
model = Experiment
class ExperimentListView(ListView):
model = Experiment
class ExperimentCreateView(CreateView):
model = Experiment
success_url = reverse_lazy('experiment_list')
fields = ['name', 'student_n', 'expert_n', 'student_cmd_n', 'expert_cmd_n']
class ExperimentUpdateView(UpdateView):
model = Experiment
success_url = reverse_lazy('experiment_list')
fields = ['is_active' ]
class ExperimentDeleteView(DeleteView):
model = Experiment
success_url = reverse_lazy('experiment_list')
The models.py is defined in the following way:
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models
from django.core.validators import MaxValueValidator, MinValueValidator
from django.utils.encoding import python_2_unicode_compatible
from django.contrib.auth.models import User
from django.db import models
from jsonfield import JSONField
import collections
class Experiment(models.Model):
name = models.CharField(max_length=200)
student_n = models.IntegerField(default=0,
validators=[MinValueValidator(0)])
expert_n = models.IntegerField(default=0,
validators=[MinValueValidator(0)])
student_cmd_n = models.IntegerField(default=2,
validators=[MinValueValidator(2)])
expert_cmd_n = models.IntegerField(default=1,
validators=[MinValueValidator(1)])
is_active = models.BooleanField(default=True)
replications = JSONField(load_kwargs={'object_pairs_hook': collections.OrderedDict},
blank=True)
Finally, the template experiment_form.html:
{% extends "vacs/base.html" %}
{% load widget_tweaks %}
{% block content %}
{% if form.errors %}
<div class="alert alert-danger" role="alert">
Form error!
</div>
{% endif %}
<form method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit" />
</form>
{% endblock %}
Thank you!
You need to override the save method for the model.
So to run clean_fields() and clean() Add the following method to your model
def save(self, *args, **kwargs):
self.full_clean()
return super().save(*args, **kwargs)
Three types of cleaning methods are run during form processing. These are normally executed when you call the is_valid() method on a
form.
Django documentation
Related
I'm a Django beginner and try to implement a class based CreateView with two forms and an OneToOne relation but have no glue how to do this in one view.
For example: the following is given:
#models.py
# some imports..
class RedItem(models.Model):
name = models.Charfield(max_length=255)
storage = models.OneToOneField("Storage", on_delete=models.CASCADE)
class BlueItem(models.Model):
name = models.Charfield(max_length=255)
storage = models.OneToOneField("Storage", on_delete=models.CASCADE)
class Storage(models.Model):
shelf = models.Charfield(max_length=255)
room = models.Charfield(max_length=255)
...the View:
#views.py
from django.views.generic.edit import CreateView
from .models import RedItem, BlueItem, Storage
# some other imports..
class RedItemCreate(CreateView):
model = RedItem
fields = ['name']
template_name_suffix = "_form"
```html
and a Form:
```html
<form method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Save">
</form>
The question is now how can a user fill in both (e.g. RedItem + Storage) in one batch?
Thanks in advance for any direction.
Update and a working approach:
I have to create forms in forms.py and add it the the CreateView.
#urls.py
from django.urls import path
from .views import *
urlpatterns = [
path('reditemlist/', ExampleList.as_view(), name='example_list'),
path('reditemcreate/', ExampleCreate.as_view(), name='example_create')
]
#forms.py
from django.forms import ModelForm
from .models import RedItem, Storage
class RedItemForm(ModelForm):
class Meta:
model = RedItem
fields = ['name']
class StorageForm(ModelForm):
class Meta:
model = Storage
fields = ['shelf']
And the CreateView:
#views.py
from django.shortcuts import render
from django.views.generic.list import ListView
from django.views.generic.edit import CreateView
from .models import *
from .forms import *
from django.urls import reverse_lazy
class ExampleList(ListView):
model = RedItem
class ExampleCreate(CreateView):
model = RedItem
form_class = RedItemForm
success_url = reverse_lazy('example_list')
def get_context_data(self, **kwargs):
context = super(ExampleCreate, self).get_context_data(**kwargs)
if self.request.POST:
context['reditem'] = RedItemForm(self.request.POST)
context['storage'] = StorageForm(self.request.POST)
else:
context['reditem'] = RedItemForm()
context['storage'] = StorageForm()
return context
def form_valid(self, form):
context = self.get_context_data()
storage = context['storage']
if storage.is_valid() and form.is_valid():
f = form.save()
shelf = storage.save(commit = False)
shelf.reditem = f
shelf.save()
return super().form_valid(form)
<form method="post">{% csrf_token %}
{{ form.as_p }}
{{ storage.as_p }}
<input type="submit" value="Save">
</form>
Next stop: UpdateView
I've followed the process laid out on the Django ModelForm's documentation, as well as a couple of tutorials. My field displays as a standard text-entry, rather than a drop down. Can anyone point me in the right direction?
forms.py
from .models import Authority # change to rating!!!
from django import forms
class AuthorityForm(forms.ModelForm):
class Meta:
model = Authority
fields = ('authority_name',)
views.py
from django.shortcuts import render
from django.views.generic import CreateView
from .models import Authority
from .forms import AuthorityForm
# Create your views here.
class HomeCreateView(CreateView):
model = Authority
form_class = AuthorityForm
template_name = 'home.html'
success_url = 'home.html'
models.py
from django.db import models
# Create your models here.
class Authority(models.Model):
authority_name = models.CharField(max_length = 100)
authority_url = models.URLField()
def __str__(self):
return self.authority_name
class Rating(models.Model):
authority_name = models.CharField(max_length = 100)
ratings = models.CharField(max_length = 20000)
# Could this be reduced if remove the trailing letters of non-int values?
# Perhaps all values could be one-hot encoded
home.html
<h2>Choose Authority:</h2>
<form method="post" novalidate>
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<button type="submit">Submit</button>
</form>
UPDATE
Hello friends,
I want to ask you for help.
I created a web form that generates PDF files. Everything is fine. Automatically Send PDFs via Email is OK. Unfortunately, the form fields that are not added to the models.Model are not included in the PDF (contents).
PDF documents display (postal_code) as blank field.
I don't know what to do. Where is the problem?
model.py
class Order(models.Model):
name = models.CharField(max_length=50)
forms.py
CHOICES=[('item1','1xx'),
('item2','2xx')]
class OrderCreateForm(forms.ModelForm):
postal_code = forms.ChoiceField(choices=CHOICES, widget=forms.RadioSelect())
class Meta:
model = Order
fields = ['name', 'postal_code']
create.html
<form action="." method="post" class="order-form">
{{ form.as_ul }}
<p><input type="submit" value="Send"></p>
{% csrf_token %}
</form>
tasks.py
from celery import task
from django.shortcuts import get_object_or_404
from oferty.models import Order
from django.template.loader import render_to_string
from django.core.mail import EmailMessage
from django.conf import settings
import weasyprint
from io import BytesIO
#task
def order_created(order_id):
order = Order.objects.get(id=order_id)
subject = 'NEW {}'.format(order.id)
message = 'Hi {}!\n\nBlablabla.\
Ident blabla {}.'.format(order.imię,
order.id)
email = EmailMessage(subject,
message,
'admin#myshop.com',
[order.email])
html = render_to_string('orders/order/pdf.html', {'order': order})
out = BytesIO()
stylesheets = [weasyprint.CSS(settings.STATIC_ROOT + 'css/pdf.css')]
weasyprint.HTML(string=html).write_pdf(out,
stylesheets=stylesheets)
email.attach('order_{}.pdf'.format(order.id),
out.getvalue(),
'application/pdf')
email.send()
pdf.html
<html>
<body>
<h1>Test</h1>
<p>
{{ order.name }}<br>
{{ order.postal_code }}
</p>
</body>
</html>
Do you give me any hint of which way ?
Your help would be greatly appreciated.
UPDATE
views.py
from django.shortcuts import render
from .models import OrderItem
from .forms import OrderCreateForm
from cart.cart import Cart
from .tasks import order_created
from django.contrib.admin.views.decorators import staff_member_required
from django.shortcuts import get_object_or_404
from .models import Order
from django.conf import settings
from django.http import HttpResponse
from django.template.loader import render_to_string
import weasyprint
def order_create(request):
cart = Cart(request)
if request.method == 'POST':
form = OrderCreateForm(request.POST)
if form.is_valid():
order = form.save()
for item in cart:
OrderItem.objects.create(order=order,
product=item['product'],
price=item['price'],
quantity=item['quantity'])
cart.clear()
order_created.delay(order.id)
return render(request,
'orders/order/created.html',
{'order': order})
else:
form = OrderCreateForm()
return render(request,
'orders/order/create.html',
{'cart': cart, 'form': form})
#staff_member_required
def admin_order_pdf(request, order_id):
order = get_object_or_404(Order, id=order_id)
html = render_to_string('orders/order/pdf.html',
{'order': order})
response = HttpResponse(content_type='application/pdf')
response['Content-Disposition'] = 'filename= "order_{}.pdf"'.format(order.id)
weasyprint.HTML(string=html).write_pdf(response, stylesheets=[weasyprint.CSS(settings.STATIC_ROOT + 'css/pdf.css')])
return response
urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^create/$',
views.order_create,
name='order_create'),
url(r'^admin/order/(?P<order_id>\d+)/pdf/$',
views.admin_order_pdf,
name='admin_order_pdf'),
]
Environment:
Django Version: 1.10.6
Python Version: 3.5.2
Help. Seriously.
Assuming you want to store the postal code on the order you'll need to add the 'postal_code' field present on OrderCreateForm to the Order model.
models.py
class Order(models.Model):
CHOICES = [
('item1', '1xx'),
('item2', '2xx')
]
name = models.CharField(max_length=50)
postal_code = models.CharField(max_length=50, choices=CHOICES)
After that you'll only need to specify the radio select widget for that field your model form.
forms.py
class OrderCreateForm(forms.ModelForm):
class Meta:
model = Order
fields = ['name', 'postal_code']
widgets = {
'postal_code': forms.RadioSelect()
}
since postal_code is not a field of order model.You will have to pass it's value separately.Or you can add the field in the model. if you choose not to include this might help.
from celery import task
from oferty.models import Order
from django.template.loader import render_to_string
from django.core.mail import EmailMessage
from django.conf import settings
import weasyprint
from io import BytesIO
#task
def order_created(order_id,postal_code):
order = Oferta.objects.get(id=order_id)
subject = 'New nr {}'.format(order.id)
message = 'Hallow, {}!\n\nBlaBlaBla.\
BlaBlaBla {}.'.format(order.imię,
order.id)
email = EmailMessage(subject,
message,
'admin#myshop.com',
[order.email])
html = render_to_string('orders/order/pdf.html', {'order': order,'postal_code':postal_code})
out = BytesIO()
stylesheets = [weasyprint.CSS(settings.STATIC_ROOT + 'css/pdf.css')]
weasyprint.HTML(string=html).write_pdf(out,
stylesheets=stylesheets)
email.attach('order_{}.pdf'.format(order.id),
out.getvalue(),
'application/pdf')
email.send()
call the function with both the values
order_created(order_id=order_id,postal_code= postal_code)
and in the pdf.html replace
{{order.postal_code}}
with
{{postal_code}}
So I have created a a dynamic formset that allows the user to add or remove as many forms as they want. Now, I am trying to do custom validation in a field(block_name) in the forms.
It seems like it is working because if the user input doesn't match a certain regex, then the data will not be saved. The problem is that there is no message showing that the input is wrong.
Also, if the user inputs incorrect data and tries to submit the page will redirect to itself and erase all the input data. How can I make the page stay in the same view if the input is wrong and also show error messages?
forms.py:
import re
from django import forms
from django.forms.models import modelformset_factory
from inventory.models import Block
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
# Form for block requests
class BlockForm(forms.ModelForm):
block_name = forms.CharField(required=True, validators= [RegexValidator('^s\d{3}rf\d*b\d*e\d+r\d+w\d*[cgls][abcdex][ed][hv][sbaec][a-d] [a-d][0-7][apfg]a', message="Please enter valid block name", code="invalid_name")])
def __init__(self, *args, **kwargs):
super(BlockForm, self).__init__(*args, **kwargs)
self.empty_permitted = False
class Meta:
model = Block
fields = ['block_name', 'block_derivatives', 'block_subsystems', 'owners']
def clean_block_name(self):
print self.cleaned_data
block_name = self.cleaned_data.get('block_name')
if block_name == "a":
print ("block name is a")
raise forms.ValidationError(
('Please enter a block name'))
return block_name
models.py:
import datetime
import re
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
from django.db import models
from django.utils import timezone
class Inventory(models.Model):
class Meta:
verbose_name_plural = "Inventories"
inventory_name = models.CharField(max_length=100)
pub_date = models.DateTimeField('date published')
def __str__(self): # __unicode__ on Python 2
return self.inventory_name
def was_published_recently(self):
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
class Block(models.Model):
class Meta:
verbose_name_plural = "Request Blocks"
inventory = models.ForeignKey(Inventory, null=True)
block_status = models.CharField(max_length=20, blank=False)
#617block_name = models.CharField(max_length=50, blank=False, null=False, validators=[block_nameTest])
block_name = models.CharField(max_length=50, blank=False, null=False)
block_derivatives = models.CharField(max_length=100)
block_subsystems = models.CharField(max_length=40)
owners = models.CharField(max_length=100)
def __str__(self):
return self.block_name
def block_owners(self):
return str(self.owners)
views.py:
from django.forms.models import modelformset_factory
from django.forms.formsets import formset_factory
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.views import generic
from django.http import HttpResponse
from django.shortcuts import render_to_response
from .models import Inventory, Block
from .forms import BlockForm
# Create your views here.
def requests(request, inventory_id):
BlockFormSet = formset_factory(BlockForm, extra=1)
inventory = get_object_or_404(Inventory, pk=inventory_id)
formset = BlockFormSet(request.POST)
if request.method == 'POST':
formset = BlockFormSet(request.POST)
if formset.is_valid():
for form in formset:
print form
form.save()
print "Success"
return HttpResponseRedirect('/inventory/2')
else:
print "Yo, this stuff is not validated"
else:
print "LOLOLOLOL"
return render(request, 'inventory/requests.html', {'inventory': inventory, 'formset': BlockFormSet})
requests.html:
{% block content %}
<div class="requestForm">
<form id="blockForm" class="original" action="{% url 'inventory:requests' inventory.id %}" method="post">{% csrf_token %}
<!-- Add New Row -->
{{formset.management_form}}
{% for form in formset %}
<div class='item'>
<ul>{{ form.as_table}}<ul>
<p style=""><a class="delete" href="#">Delete</a></p>
</div>
{% endfor %}
<p><a id="add" href="#">Add another item</a></p>
<input type="submit" name="submit" value="Request Blocks" id="submitButton">
</form>
</div>
{% endblock %}
You're redirecting after an invalid POST, rather than redisplaying the same form. Even though your redirect is to the same view, it loses the POST data and the form is therefore blank.
Drop the first else block, and let execution fall through to the final render line.
I'm trying to ask a user some additional info while signing up. I'm using django allauth for authorization and authentication. I try to add three more fields during the signup process. If If I run it, it shows me the standard form plus gender field. However, it doesn't seem to really work. How can I save the data? Could someone help? Thank you in advance!
EDITED: if I just use
if form.is_valid():
form.save()
return redirect('/success/')
I get an error:
save() missing 1 required positional argument: 'user'
I'm quite new to django.
I created signups app in the project.
I put this in allauth_settings.py:
ACCOUNT_SIGNUP_FORM_CLASS = 'signups.forms.MySignupForm'
My signups/model.py:
from django.contrib.auth.models import User
from django.db import models
from allauth.account.models import EmailAddress
from allauth.socialaccount.models import SocialAccount
import hashlib
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name='profile')
about_me = models.TextField(null=True, blank=True)
timestamp = models.DateTimeField(auto_now_add= True, auto_now=False)
updated = models.DateTimeField(auto_now_add= False, auto_now=True)
GENDER_CHOICES = (
('m', 'Male'),
('f', 'Female'),
)
# gender can take only one of the GENDER_CHOICES options
gender = models.CharField(max_length=1, choices=GENDER_CHOICES,
verbose_name='Gender')
def __unicode__(self):
return self.user.username
class Meta:
db_table = 'user_profile'
def profile_image_url(self):
"""
Return the URL for the user's Facebook icon if the user is logged in via
Facebook, otherwise return the user's Gravatar URL
"""
fb_uid = SocialAccount.objects.filter(user_id=self.user.id, provider='facebook')
if len(fb_uid):
return "http://graph.facebook.com/{}/picture?width=40&height=40".format(fb_uid[0].uid)
return "http://www.gravatar.com/avatar/{}?s=40".format(hashlib.md5(self.user.email).hexdigest())
def account_verified(self):
"""
If the user is logged in and has verified hisser email address, return True,
otherwise return False
"""
if self.user.is_authenticated:
result = EmailAddress.objects.filter(email=self.user.email)
if len(result):
return result[0].verified
return False
User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u)[0])
my signups/forms.py:
from allauth.account.forms import SignupForm
from django import forms
from .models import UserProfile
class MySignupForm(SignupForm):
class Meta:
model = UserProfile
gender = forms.CharField(max_length=1, label='gender')
def save(self, user):
user.gender = self.cleaned_data['gender']
user.save()
my signups/views.py:
from django.template import RequestContext
from django.shortcuts import render_to_response
from .forms import SignupForm
def index(request):
form = MySignupForm(request.POST or None)
if form.is_valid:
???
return render_to_response("signups/index.html", locals(),
context_instance=RequestContext(request))
My index.html is very basic, I just wanted to see the representation of the form:
{% extends 'account/base.html' %}
{% block head_title %}ProjectName{% endblock %}
{% block content %}
<form method="POST" action="">
{{ form.as_p }}
<input type="submit">
</form>
{% endblock %}
You are instantiating the SignupForm, which is the standard form but not your MySignupForm in the view. Change it like this:
def index(request):
form = MySignupForm()
return render_to_response("signups/index.html", locals(),
context_instance=RequestContext(request))