Django inline formset custom validation - python

Okay. I am probably just overlooking something silly. But I am having an issue with an inline form in django. I have this:
class BaseModelAFormset(forms.models.BaseInlineFormSet):
def clean(self):
for form in self.forms:
print 'I am getting here'
ModelAFormset = inlineformset_factory(ModelB,ModelA, extra=1, formset=BaseModelAFormset)
My models are not really named ModelA and ModelB. I did that as an example.
Anyway, I have this formset on a page and have 2 forms in it plus a third blank one. (So two of the three forms are filled in with objects.
I hit save and I'd expect to see two "I am getting here" statements, but I only see one.
In the end, I am doing a count check for validation, but if the second form is never checked, my count check won't do much good. So the print statement is an intermediate step towards that. Anyone have any idea why this isn't looping right?

Related

Django form test non-field errors

I'd like to ask for a method to check if any kind of non-field errors was raised during form validation in Django. So far I found solution to do this during view testing. Well, it wasn't what I was looking for since I'm interested in doing it at form tests.
I know the way to look for fields' errors:
class FormTest(TestCase)
def test_fields_errors(self):
"""
Suppose form has one field which has to be filled
"""
form = TestForm()
form.full_clean()
self.assertFalse(form.is_valid())
self.assertIsInstance(
form.errors.as_data()['required_field'][0],
ValidationError
)
self.assertEquals(
form.errors['deadline_datetime'][0],
'This field must not be empty'
)
Is there any way to do this when we deal with non-field errors?
I answer my own question, but I also wanna start discussion if there is any better solution
With small help of debugger, I found that non-field errors appear in form.errors as well. But this time there wasn't any dict key named like non-field errors. All I found was a bit strange key __all__, but it allowed me to access demanded errors:
class FormTest(TestCase)
def test_fields_errors(self):
"""
Suppose form has two fields and either one has to be filled
"""
form = TestForm()
form.full_clean()
self.assertFalse(form.is_valid())
self.assertIsInstance(
form.errors.as_data()['__all__'][0],
ValidationError
)
assertEquals(
errors['__all__'][0],
'Fill at least one field'
)
So, I was able to find the error I was looking for during testing non-field errors raise.

Passing data through Django forms

I feel I'm missing the obvious but I can't work it out!
I have written a custom form (for use outside of django admin), which I want to use to create / update instances of a number of model instances as well as hold conditional fields. However I seem to be losing my conditional data.
In my view I instansiate an instance of my form and pass it into the request context:-
view.py
form = MyForm(my_bool=True, pid=7)
render(request 'my_page.html', {'form': form})
forms.py
class MyForm(forms.Form):
def __init__(self, *args, **kwargs):
my_bool = kwargs.pop('my_bool', False)
self.pid = kwargs.pop('pid', None)
super(MyForm, self).__init__(*args, **kwargs)
if my_bool:
self.fields['textbox'] = forms.CharField(max_length=256)
That all works fine and the form renders as expected. Now when I submit the form it hits the below
view.py
if request.method == 'POST':
form = MyForm(request.POST)
if form.is_valid():
# Do Stuff
My problem is that the form object in the above code does not contain my 'textbox' or 'pid' fields even though the form I submitted did. I'm certain whatever I'm doing wrong is extremely obvious but from a whole lot of googling I cant work out a simple way to instantiate a form, pass it some data about what fields I want to show as well as say the id of the model I eventually want it to update and then have access to the info I passed in the post part of the code.
The # Do Stuff part of my code is supposed to take the 'pid' I passed to the form and use that to fetch a product e.g. Product.objects.get(pk=pid), however without storing the pid in the session I cant work out how to access it from the postback.
If a validation error occurs in my form i.e. form.is_valid() returns false I render the request again passing the form I already have, that way I see the validation errors however any conditional fields I passed initially are missing and their values ignored.
Maybe I'm just going about it completely wrong and I should be doing this a different way. What I am trying to achieve is a content entry form whose fields change depending on the type of product it is passed, once filled in the form saves the data to that instance of the product.
Any help would be greatly appreciated, I've found Django to be extremely accommodating to anything I've thrown at it so far and this feels like such a common use-case that I must just be doing it wrong!
I'm on Django v1.11 & Python v3.6.
you are passing two arguments to create the form, namely my_bool and pid. But you are not passing those arguments in the POST view. In particular my_bool is None so the textbox field never gets generated. Changing
form = MyForm(request.POST)
to
form = MyForm(request.POST, my_bool=True, pid=7)
should do the trick.

Django 'likes' - ManyToManyField vs new model

I'm implementing likes on profiles for my website and I'm not sure which would be the best practice, a ManyToManyField like so:
class MyUser(AbstractBaseUser):
...
likes = models.ManyToManyField('self', symmetrical = False, null = True)
...
or just creating a class Like, like so:
class Like(models.Model):
liker = models.ForeignKey(MyUser, related_name='liker')
liked = models.ForeignKey(MyUser, related_name='liked')
Is one of them a better choice than the other? If so, why?
thanks
The first option should be preffered. If you need some additional fields to describe the likes, you can still use through="Likes" in your ManyToManyField and define the model Likes.
Manipulating the data entries would be also somewhat more pythonic:
# returns an object collection
likes_for_me = MyUser.objects.filter(pk=1).likes
instead of:
me = MyUser.objects.filter(pk=1)
likes_for_me = Like.objects.filter(liked=me)
The second option is basically what is done internally: a new table is created, which is used to create the links between the entities.
For the first option, you let django do the job for you.
The choice is certainly more about how you want to do the requests. On the second options, you would have to query the Like models that match you model, while on the first one, you only have to request the MyUser, from which you can access the connections.
Second option is more flexible and extensible. For example, you'll probably want to track when like was created (just add Like.date_created field). Also you'll probably want to send notification to content author when content was liked. But at first like only (add Like.cancelled boolead field and wrap it with some logic...).
So I'll go with separate model.
I think the one you choose totally depends on the one you find easier to implement or better. I tend to always use the first approach, as it is more straightforward and logical, at least to me. I also disagree with Igor on that it's not flexible and extensible, you can also initiate notifications when it happens. If you are going to use the Django rest framework, then I totally suggest using the first method, as the second could be a pain.
class Post(models.Model):
like = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True, related_name='post_like')
Then in your view, you just do this.
#api_view(['GET'])
#permission_classes([IsAuthenticated])
def like(request, id):
signed_in = request.user
post = Post.objects.get(id=id)
if signed_in and post:
post.like.add(signed_in)
# For unlike, remove instead of add
return Response("Successful")
else:
return Response("Unsuccessful", status.HTTP_404_NOT_FOUND)
Then you can use the response however you like on the front end.

How to update form with errors in Django?

I'm trying to write an is_valid method for my forms and I'm using this general strategy:
def is_valid(self):
form = super(UserCreateForm, self).is_valid()
for f, error in self.errors.iteritems():
if f!= '__all__':
self.fields[f].widget.attrs.update({'class': 'error', 'value': strip_tags(error)})
return form
I want to update the form fields' attributes if I get an error with helpful attributes. But I already have class attributes for the fields (using Bootstrap, so something like 'class':'form-control'); I want the error to replace them. However, when I fail the validation and actually get an error, Django complains saying can't concatenate str and errorList. I'm a bit new to Django, so I'm not sure what is going on here.
The way i do this is, have the the errors checked at all the individual divs and display bootstrap's .error class if i find a error.
Eg:{% if form.error %}
Like that you check it with every filed name.
Note: There might be better ways to do it, but i have been using this for a long time now and has worked fine.

Django 1.1 FormWizard, Dynamically extend form

I am trying to create a multipage form, where the number of field elements on the second page is defined by the answers given on the first.
I have a formWizard set up and my understanding is that I need to use process_step() to alter the setup for the next page. I can either extend an existing form definition to add more elements, or merge 2 or more form definitions together to produce the correct number of form elements, but i have no idea how to do this.
Eg
Page 1 - Select interested subjects:
Page 2 - for each subject: ask relevant questions. Questions are defined as seperate forms in application, but need to be shown on one page, or merged into a single form.
Any help much appreiciated.
Spender
Spender,
At least at the moment I don't know a way of merging multiple forms onto one page in a FormWizard. In django 1.2 you will be able to include FormSets as steps in FormWizards (as per this ticket) but those only deal with multiple copies of identical forms, not compilations of many forms. But there is a way to do what you ask:
from django.contrib.formtools.wizard import FormWizard
from django import forms
class SubjectForm(forms.Form):
subjects = forms.MultipleChoiceField(choices = (('language', 'language'),
('sport','sport')))
class RelatedQForm(forms.Form):
"""Overload the __init__ operator to take a list of forms as the first input and generate the
fields that way."""
def __init__(self, interested_subjects, *args, **kwargs):
super(RelatedQForm, self).__init__(*args, **kwargs)
for sub in interested_subjects:
self.field[sub] = forms.CharField(label = "What do you think about %s" % subject)
class SubjectWizard(FormWizard):
def done(self, request, form_list):
process_form_list(form_list)
def process_step(self, request, form, step):
if step == 1:
chosen_subs = form.cleaned_data['subjects']
self.form_list[1] = RelatedQForm(chosen_subs)
With this code you instantiate your FormWizard as you normally would in the view and then let the wizard class take care of everything behind the scenes.
The general idea is to overload the init class of a "RelatedQForm" to dynamically alter the fields. This code snippet was taken from here. You can make the processing within the init operator as complex as you'd like, read "include the fields from your forms as if-elif blocks inside the for-loop" ... you could probably even figure out a way to strip the fields from your current forms programatically, I'd have to see them to figure it out though.
Your "process_form_list" function will need to loop over the fields using something like:
for field, val in form.cleaned_data.items():
do_stuff
Hope this gets you on your way :)
I don't think the
self.form_list[1] = RelatedQForm(chosen_subs)
part works. I always get the error message:
object is not callable
It seems to be like form_list only accepts RelatedQForm (the name of the form), not an instance of it.

Categories