0

So I've spent the day trying to chase down a custom thing that I wanted to achieve using FormView. When I use FormView with HTML form method="POST", I am able to get the desired result, mostly.

If a user clicks to submit a form and an empty field exists, they get an error message telling them the form is required. I'm aware that I can make the ModelChoiceField required on the form, but was trying to implement my own logic for this.

I found through my searching that if I'm not updating anything via the form, form method="GET" may be more appropriate. So I switched to that, and basically got this method working, but my error logic still doesn't quite work the way I'd expect.

Long story short, I can use the form method="GET", but when I try to do validation on the form, it doesn't work. Worse yet, if I try to include an empty label, that creates an invalid literal message, because based on this SO...In a Django template, how to specify a dictionary key which is itself an attribute? It doesn't seem possible to specify error logic/validation if the first field is essentially empty, which it is in order to create a blank first choice on ModelChoiceField.

Here's my code...

The Form...

dropdown = forms.ModelChoiceField(queryset=Author.objects.filter(is_active=True),required=False)

def __init__(self, *args, **kwargs):
    # q = kwargs.pop('dropdown', None)
    dropdown = kwargs.pop('dropdown', None)
    super(AuthorByNameForm, self).__init__(*args, **kwargs)
    self.fields['dropdown'].empty_label = ''

The HTML...

<form method="GET" action="{% url 'Main:author_detail' %}">

  <h1 class="class">View By Author</h1>

  <div>
    {{ form.dropdown }}
  </div>

The Views...FORMVIEW

class AuthorByNameView(LoginRequiredMixin,FormView):
    form_class = AuthorNameForm
    template_name = 'author_by_name.html'

 def get_form_kwargs(self):
     kwargs = super(AuthorByNameView, self).get_form_kwargs()
     kwargs['dropdown'] = self.request.GET.get("dropdown")
     return kwargs

DetailView....

class AuthorDetailView(LoginRequiredMixin,DetailView):
    model = Author
    template_name = 'author_detail.html'

def get_object(self, queryset=None):
    return get_object_or_404(Author, id=self.request.GET.get("dropdown"))
    return get_object_or_404

Model...

Essentially just User...with a One to One with UserProfile...

class UserProfile(models.Model):
    user = models.OneToOneField(User,on_delete=models.CASCADE)

Essentially, if I removed the empty_label line from my form, the form validates, provided that the queryset isn't empty. If I include the empty_label line of code and the user clicks on the submit button without selecting anything, they get an invalid literal for int() with base 10: '' message. It seems the only way to work around this is to make the ModelChoiceField required=True, with the empty_label reference.

Using the request.GET appears to behave differently than the POST for good reason, I'm aware. When I used the POST instead, everything works exactly as I want it to, except there doesn't appear to be a way to prevent the error message that I'm using to show up if the user clicks on the BACK button.

I explored some Javascript solutions today that were recommeneded via this SO, How To Clear self.add_error in Django if user clicks on browser back button? but to no avail.

Has anyone else encountered this struggle? Based on my knowledge up until this point, in this case since I'm not updating the records in question and just displaying them, form method="GET" would seem to make the most sense, but I seem to be pretty limited as to my options on how I can keep a blank first position in my ModelChoiceField and also do any kind of my own validation on the field in question without encountered the invalid literal message.

Thanks in advance for any thoughts.

Steve Smith
  • 1,019
  • 3
  • 16
  • 38

2 Answers2

0
def get_object(self, queryset=None):
    foo = get_object_or_404(Author, id=self.request.GET.get("dropdown"))
    return foo

Um, I'm a little baffled myself, but this doesn't look right, the two return statements. Try something like above. Also:

  self.fields['dropdown'].empty_label = None
Jay
  • 1,289
  • 3
  • 11
  • 22
  • thanks for the suggestions. I changed the get_object but it didn't make a difference. I also change the empty_label reference...And that's the issue. I want to have an empty label...a blank space at the onset of the form...If I include the empty label as being '', that's what causes the problem...it errors out when the user clicks submit and they haven't selected anything. Your empty label reference essentially fixes my problem for querysets that aren't empty...but if they are, I want to do validation if the user clicks submit on an empty field. Keep getting the invalid literal. – Steve Smith Jul 23 '19 at 03:54
  • So you're just trying to have the form work without showing the label on the front end. As in "Name: [ input field ], Password [ input field ]: [ input field ]?? You want the third label to be blank, am I understand this correctly? – Jay Jul 23 '19 at 04:07
  • Not sure we're on same page. I have one form with one dropdown field. In a perfect world, I'd like to have the form load with a blank label. Then if user clicks to submit the form and hasn't selected anything, I'd like to be able to validate that and send an error message to the view. In this case, using GET, there doesn't seem to be a way to validate the form, and after the user clicks submit and it's blank it immediately goes to the invalid literal error. If I do POST, I do in fact get to validate the dropdown. – Steve Smith Jul 23 '19 at 04:16
  • Seems that GET and ModelChoiceField with empty label = '' just don't seem to go together. – Steve Smith Jul 23 '19 at 04:16
  • so, assuming you were using a password input field with the label 'password' instead of a modelchoicefield, you would, in that case, want the input box to load and process without the word 'password' in front of it?? Is that correct? – Jay Jul 23 '19 at 04:19
  • No. In this case, the empty_label reference is a reference to the ModelChoiceField...that allows a developer to conceivably specify if they want there to be a space or not in the dropdown field. You suggested I try empty_label=None..which would mean there is no blank field on the dropdown itself...not the label in front of the field...the label in the field... – Steve Smith Jul 23 '19 at 04:23
  • Oh, okay. I get it. If Dropdown is 1) Cat 2) Dog 3) Rat, you don't want the initial choice to be any of them, you want the blank input. Let me play around with it a bit. The word "label" threw me off. – Jay Jul 23 '19 at 04:25
  • Exactly. You got it now. If you use POST in the HTML...django seems to allow for validation...GET, not so much. My workaround is to use POST....but it seems like it should be a GET scenario...POST allows the checking/validation in the forms...but GET doesn't seem to. – Steve Smith Jul 23 '19 at 04:27
  • This may sound like a silly suggestion, but have you tried ' ' instead of '' ?? – Jay Jul 23 '19 at 04:30
  • Instead of where? – Steve Smith Jul 23 '19 at 04:34
  • Yes I tried ' ' and '' and variations....it seems that because there is no number, that is why it's saying it's an invalid literal...the issue is because it's a null value.... – Steve Smith Jul 23 '19 at 04:35
  • But of course that's the whole point of creating the empty label....seems like either a bug or a design challenge. – Steve Smith Jul 23 '19 at 04:35
  • Yeah, I'm flummoxed, weird it works with Post and not GET though. I'm going to keep hacking at it, and I'll let you know ... I feel oddly challenged right now. – Jay Jul 23 '19 at 04:41
  • (of course you could always use AJAX , but I'm sure you already new that) – Jay Jul 23 '19 at 04:44
0

Leveraging this SO....How to prevent submitting the HTML form's input field value if it empty I was able to determine that the best way to go about this was to add the following Javascript to by my code...

$('form').submit(function() {
  var dropdown = $('#id_dropdown').val();
  if (dropdown === undefined || dropdown === "") {
    $('#id_dropdown').attr('name', '' );
  } else {
    $('#id_dropdown').attr('name', 'dropdown');
  }
});
  });

The code above forces a 404 instead of the invalid literal message that I was getting before.

Steve Smith
  • 1,019
  • 3
  • 16
  • 38