I am trying to add validation to my Django forms.
I want to require at least one child model for my inlineformset.
I am using the following as a reference:
Inline Form Validation in Django
I am still getting an error
line 454, in formset_factory
return type(form.name + str('FormSet'), (formset,), attrs) TypeError: metaclass conflict: the metaclass of a derived class must
be a (non-strict) subclass of the metaclasses of all its bases
from django.forms import inlineformset_factory
from .models import *
from django.forms.models import BaseInlineFormSet
from django import forms
class PropertyForm(forms.ModelForm):
class Meta:
model = Property
exclude = ()
class UnitForm(forms.ModelForm):
class Meta:
model = Unit
exclude = ()
class CustomUnitFormset(BaseInlineFormSet):
def is_valid(self):
return super(CustomUnitFormset, self).is_valid() and \
not any([bool(e) for e in self.errors])
def clean(self):
# get forms that actually have valid data
count = 0
for form in self.forms:
try:
if form.cleaned_data and not form.cleaned_data.get('DELETE', False):
count += 1
except AttributeError:
pass
if count < 1:
raise forms.ValidationError('You must have at least one unit')
UnitFormSet = inlineformset_factory(Property, Unit, form=PropertyForm,
formset='CustomUnitFormset',
extra=0,
min_num=1,
validate_min=True,
validate_max=True,
max_num=10,
)
view.py
class PropertyUnitCreate(LoginRequiredMixin, CreateView):
model = Property
fields = ['address']
success_url = reverse_lazy('property-list')
def get_context_data(self, **kwargs):
data = super(PropertyUnitCreate, self).get_context_data(**kwargs)
if self.request.POST:
data['units'] = UnitFormSet(self.request.POST)
else:
data['units'] = UnitFormSet()
return data
def form_valid(self, form):
context = self.get_context_data()
units = context['units']
form.instance.user = self.request.user
with transaction.atomic():
self.object = form.save()
if units.is_valid():
units.instance = self.object
units.save()
return super(PropertyUnitCreate, self).form_valid(form)
I have also tried to understand the inheritance issues explained here:Triple inheritance causes metaclass conflict... Sometimes
My view logic comes from https://medium.com/#adandan01/django-inline-formsets-example-mybook-420cc4b6225d
But I dont see where i have a class inheriting from two classes that would cause this error. Everything works fine if i remove the " formset='CustomUnitFormset',
"
Can someone explain the reason for the error and how i could prevent this? Could i be managing my forms.py file better?
You need to pass the actual formset class, not a string, to the factory.
UnitFormSet = inlineformset_factory(Property, Unit, form=PropertyForm,
formset=CustomUnitFormset, ...)
Related
Consider the these two models and view:
models.py
class BHA_List(models.Model):
well = models.ForeignKey(WellInfo, 'CASCADE', related_name='bha_list')
bha_number = models.CharField(max_length=100)
class BHA_overall(models.Model):
bha_number = models.ForeignKey(BHA_List, 'CASCADE', related_name='bha_overall')
drill_str_name = models.CharField(max_length=111)
depth_in = models.CharField(max_length=111)
views.py
class BHA_UpdateView(UpdateView):
model = BHA_overall
pk_url_kwarg = 'pk_alt'
form_class = BHA_overall_Form
To my understanding, pk_url_kwarg = 'pk_alt' will query and return instances of model = BHA_overall.
Let's say that I use a different CBV other than UpdateView, and want to implement two models. So something like this:
model = (BHA_overall, BHA_List). Is there any way that I force my pk_url_kwarg = 'pk_alt' to query and return instances only in BHA_List, but force my get_object() return objects in BHA_overall?? What CBV should I use?
you can use just 'View' and define methods post and get.
about like this:
class SomeView(View):
model = BHA_List
template_name = 'some.html'
def get(request, **kwargs):
overall = BHA_overall.objects.all()
return render(request,
self.template_name,
locals())
def get(request, **kwargs):
return render(request, self.template_name, {})
I'm trying to update an model's fields with save method:
forms.py:
class FooForm(forms.ModelForm):
class Meta:
model = Foo
exclude = ['created']
views.py:
class FooView(FormView):
success_url = '/some/url/'
template_name = 'some/template.html'
form_class = FooForm
model = Foo
def form_valid(self, form):
if 'foo' in self.request.session:
pk = self.request.session['foo']
foo = Foo.objects.get(pk=pk)
self.object = form.save(instance=foo)
else:
self.object = form.save()
self.request.session['foo'] = self.object.pk
return HttpResponseRedirect(self.get_success_url())
I have an error, when i try to use form.save(instance=foo):
TypeError at /some/url
save() got an unexpected keyword argument 'instance'
What is my problem?
Django 1.9.0
Python 3.4.3
If you read the very documentation that you linked, you will see that the instance keyword is an input to the ModelForm constructor and not the save method.
You will need to do something like
form = FooForm(instance=foo)
form.save()
You should provide an instance to the constructor of the form, not to save method.
To make it works, you should use UpdateView.
def form_valid(self, form):
if 'foo' in self.request.session:
pk = self.request.session['foo']
foo = Foo.objects.get(pk=pk)
foo_form = self.form_class(form.cleaned_data, instance=foo)
foo_form.save()
I use modelformset_factory, and I use full_clean() to validate the form with unique_together=True. I wonder what is the best way to handle error in case the unique_together do not validate in order to return the error message in the template.
Please take a look to my view, and tell me if im correct the way I do it, or if there is a better approach.
model:
class Attribute(models.Model):
shapefile = models.ForeignKey(Shapefile)
name = models.CharField(max_length=255, db_index=True)
type = models.IntegerField()
width = models.IntegerField()
precision = models.IntegerField()
def __unicode__(self):
return self.name
def delete(self):
shapefile = self.shapefile
feature_selected = Feature.objectshstore.filter(shapefile=shapefile)
feature_selected.hremove('attribute_value', self.name)
super(Attribute, self).delete()
class Meta:
unique_together = (('name', 'shapefile'),)
form:
class AttributeForm(ModelForm):
def __init__(self, *args, **kwargs):
super(AttributeForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.pk:
self.fields['type'].widget.attrs['disabled'] = True
self.fields['type'].required = False
self.fields['width'].widget.attrs['readonly'] = True
self.fields['precision'].widget.attrs['readonly'] = True
def clean_type(self):
if self.instance and self.instance.pk:
return self.instance.type
else:
return self.cleaned_data['type']
type = forms.ChoiceField(choices=FIELD_TYPE)
class Meta:
model = Attribute
exclude = 'shapefile'
view:
def editFields(request, shapefile_id):
layer_selected = Shapefile.objects.get(pk=shapefile_id)
attributes_selected= Attribute.objects.filter(shapefile__pk=shapefile_id)
attributesFormset = modelformset_factory(Attribute, form=AttributeForm, extra=1, can_delete=True)
if request.POST:
formset = attributesFormset(request.POST, queryset=attributes_selected)
if formset.is_valid():
instances = formset.save(commit=False)
for instance in instances:
instance.shapefile = layer_selected
try:
instance.full_clean()
except ValidationError as e:
non_field_errors = e.message_dict[NON_FIELD_ERRORS]
print non_field_errors
formset = attributesFormset(queryset=attributes_selected)
return render_to_response("basqui/manage_layer_editFields.html", {'shapefile': layer_selected, 'formset':formset}, context_instance=RequestContext(request))
instance.save()
formset = attributesFormset(queryset=attributes_selected)
return render_to_response("basqui/manage_layer_editFields.html", {'shapefile': layer_selected, 'formset':formset}, context_instance=RequestContext(request))
The disadvantage of your approach is that you have moved the validation from the form to the view.
I had the same problem recently of validating a unique together constraint where one field is excluded from the model form. My solution was to override the model form's clean method, and query the database to check the unique together constraint. This duplicates the code that is called by full_clean, but I like it because it's explicit.
I briefly thought about overriding _get_validation_exclusions which would have been more DRY, but I decided not to rely on a private api.
I have the following hierarchy of classes:
class ProfileUpdateView( UpdateView, LoggerMixin ):
def get_context_data(self, **kwargs):
context = super(ProfileCreateView, self).get_context_data(**kwargs)
...
return context
UpdateView is in fact django.views.generic.UpdateView
class EventViewMixin(object):
template_name = ...
model = Event
form_class = ...
def get_success_url(self):
return self.success_url + str(self.object.id)
Class UpdateEventView mixes ProfileUpdateView and EventViewMixin
class UpdateEventView(ProfileUpdateView, EventViewMixin):
def form_valid(self, form):
...
return super(UpdateEventView, self).form_valid(form)
The problem in that for some reason the field "model=Event" is not visible to the framework when
it tries to use UpdateEventView. So I get the error:
UpdateEventView is missing a queryset. Define UpdateEventView.model, UpdateEventView.queryset, or override UpdateEventView.get_object().
What am I missing?
DISCLAIMER: I'm sort of a newbie to Python/Django.
So my question in sort of dumb ...
The problem is in the order of the mixins:
class UpdateEventView(ProfileUpdateView, EventViewMixin):
....
must be replaced with:
class UpdateEventView( EventViewMixin, ProfileUpdateView ):
This is because ProfileUpdateView has in its inheritance tree a field "model=None",
and if ProfileUpdateView is on the first position in the that is the value that
will be considered. If EventViewMixin comes first, then the correct value is taken.
I have 2 models which are related to each other using One to One Relation. How can i display the validation error if object of one model is already related to another.
Suppose i have 2 models
Class Place(models.Model):
field1 = CharField()
field2 = CharField()
Class Restaurant(models.Model):
related_field = OneToOneField(Place)
field3 = CharField()
In my case For one place one Restaurant. If user submits new Restaurant for the same place, then a validation should raise
If Restaurant object already attached to Place object, so for second Restaurant for same Place object i want to display validation
In admin we usually see this message "Restaurent with this Place already exists." But how can i achieve the same this using modelform with generic views
class RestaurantCreateView(CreateView):
form_class = RestaurantForm
template_name = 'restaurant_create.html'
def get_success_url(self):
return reverse_lazy('restuarants', args=[self.object.id])
def get_place(self, **kwargs):
id = self.kwargs['pk']
place = Place.objects.get(id=id)
return place
def get_context_data(self, **kwargs):
context = super(RestaurantCreateView, self).get_context_data(**kwargs)
place = self.get_place()
context['place'] = place
return context
def form_valid(self, form):
self.object = form.save(commit=False)
place = self.get_place()
self.object.place = place
self.object.save()
return HttpResponseRedirect(self.get_success_url())
How can perform One To One relation validation in class based generic views?
Then you need to pass the Place object into your Restaurant form. The form validation has no access to these things defined in the view unless you explicitly pass it that data.
class MyForm(forms.ModelForm):
class Meta:
model = Restaurant
def __init__(self, *args, **kwargs):
self.place = kwargs.pop('place', None)
super(MyForm, self).__init__(*args, **kwargs
def clean_related_field(self):
place = self.place
try:
place.restaurant
except Restaurant.DoesNotExist:
return place
raise forms.ValidationError("Restaurant already exists for that book")
class MyView(...):
form_class = MyForm
def get_form_kwargs(self):
kwargs = super(MyView, self).get_form_kwargs()
kwargs['place'] = Place.objects.get(id=self.kwargs['pk'])
return kwargs
{% if form.related_field.errors %}.........{% endif %}
Another approach is to fake validation in form_valid() and set the errors dict, and return the response. This method may be preferable since it's much more contained. Also especially if you are not using the form field anyways, you might as well add an error flag to the context.
class MyView():
def form_valid(self, form):
if Restaurant.objects.filter(related_field=self.get_place()):
ctx = self.get_context_data(form=form)
ctx['massive_error'] = True
return self.render_to_response(ctx)