Manually Rendering Django Form with Validation

China☆狼群 提交于 2019-12-14 03:16:05

问题


I've started to create a form. Basically the form is a "test" of twenty words. The form consists of twenty text fields which I want to contain the definition of a word. The user would input the word. Once complete, the form should validate the data and mark what is correct and what isn't. I've done plenty of modelforms in django, but this one is different. All the data in this form has to be passed through as context.
views.py

def get_test(request, username='default'):
    template_name = 'main/test.html'
    if request.method == 'POST':
        pass
    else:
        lang = Language(config('USER'), config('PASS'))
        streakinfo = lang.get_streak_info()
        uniquewords = lang.get_unique_words()
        testwords = get_test_words(uniquewords)
        wordsdict = get_word_dict(testwords)
        form = TestForm()
        context = {
            'testwords': testwords, # list of random unique test words
            'wordsdict': wordsdict, # dict of words + definitions {word: {pronounciation, definition}}
            'form': form,
        }
    return render(request, template_name, context)

forms.py

class TestForm(forms.Form):
    word_1 = forms.CharField(label='1', max_length=100)
    word_2 = forms.CharField(label='2', max_length=100)
    word_3 = forms.CharField(label='3', max_length=100)
    word_4 = forms.CharField(label='4', max_length=100)
    word_5 = forms.CharField(label='5', max_length=100)
    word_6 = forms.CharField(label='6', max_length=100)
    word_7 = forms.CharField(label='7', max_length=100)
    word_8 = forms.CharField(label='8', max_length=100)
    word_9 = forms.CharField(label='9', max_length=100)
    word_10 = forms.CharField(label='10', max_length=100)
    word_11 = forms.CharField(label='11', max_length=100)
    word_12 = forms.CharField(label='12', max_length=100)
    word_13 = forms.CharField(label='13', max_length=100)
    word_14 = forms.CharField(label='14', max_length=100)
    word_15 = forms.CharField(label='15', max_length=100)
    word_16 = forms.CharField(label='16', max_length=100)
    word_17 = forms.CharField(label='17', max_length=100)
    word_18 = forms.CharField(label='18', max_length=100)
    word_19 = forms.CharField(label='19', max_length=100)
    word_20 = forms.CharField(label='20', max_length=100)

I mean, it's simple enough to go through and render each field manually, but what I don't know and have never done is this without a model. For instance, I want to build up a table, col 1 has the definition (I don't actually need the label=## because again, I am passing the data through as context), col 2 has the field. How do I tie the post data together such that when I post the results, col 2 is most assuredly checked against col 1? In a nutshell, how can I manually render and validate a form and keep all the data lined up? I apologize ahead of time if this is to broad of a question.

Update:

I was able to get the test data into the form and render the fields with the following (by hacking away at the forms.Form inheritance):

class TestForm(forms.Form):
    """
    Student test form
    """    
    def __init__(self, testdict, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.testdict = {} if testdict is None else testdict
        d = self.testdict
        for word in d:
            answer = word
            for key in d[word]:
                value = str(d[word][key])
                if key == 'id':
                    field_name = value
                if key == 'definition':
                    question = value
            self.fields[field_name] = forms.CharField(label=question, max_length=100)

Still need help though.


回答1:


Yes, you can override the clean mehtod and implement your validations on them like this:

You have wrtten all the fields with form class and then using get you actually get what is being entered in those fields by their name HTML parameter and then you manipulate the data by their variables and if they don't match to what exactly you desire then raise a Validationerror. Then you're creating a context of all that data from those fields into a dict and setting a variable to it which at the end is returned.

forms.py

class TestForm(forms.Form):
    word_1 = forms.CharField(label='1', max_length=100)
    word_2 = forms.CharField(label='2', max_length=100)
    word_3 = forms.CharField(label='3', max_length=100)
    word_4 = forms.CharField(label='4', max_length=100)
    word_5 = forms.CharField(label='5', max_length=100)
    word_6 = forms.CharField(label='6', max_length=100)
    word_7 = forms.CharField(label='7', max_length=100)
    word_8 = forms.CharField(label='8', max_length=100)
    word_9 = forms.CharField(label='9', max_length=100)
    word_10 = forms.CharField(label='10', max_length=100)
    word_11 = forms.CharField(label='11', max_length=100)
    word_12 = forms.CharField(label='12', max_length=100)
    word_13 = forms.CharField(label='13', max_length=100)
    word_14 = forms.CharField(label='14', max_length=100)
    word_15 = forms.CharField(label='15', max_length=100)
    word_16 = forms.CharField(label='16', max_length=100)
    word_17 = forms.CharField(label='17', max_length=100)
    word_18 = forms.CharField(label='18', max_length=100)
    word_19 = forms.CharField(label='19', max_length=100)
    word_20 = forms.CharField(label='20', max_length=100)


    def clean(self):

        word_1 = self.cleaned_data.get("word_1")
             #        |
             #        |         write the clean method of all fields
             #        |
             #      ----- 
             #       --- 
             #        - 

        word_20 = self.cleaned_data.get("word_20")



        if word_1 and word_2 and word_7 and word_15 != something:
            raise forms.ValidationError("Something Fishy")
            # i combined few of the word fields but you check all the fields separately also and implement your validation.

        words_context = {
            'word_1':word_1

            #     |               <--write all the context of corresponding fields
            #     |

            'word_20':word_20
        }

        return words_context

Views.py

def get_test(request, username='default'):
    template_name = 'main/test.html'
    form = TestForm()
    if request.method == 'POST':
        if form.is_valid():
            word_1 = self.cleaned_data.get("word_1")
             #        |
             #        |         write the clean method of all fields
             #        |
             #      ----- 
             #       --- 
             #        - 
            word_20 = self.cleaned_data.get("word_20")
            newtest = Test(word_1=word_1,....word_20=word_20)
            newtest.save()
            return redirect('whereever you want to redirect')
    else:
        lang = Language(config('USER'), config('PASS'))
        streakinfo = lang.get_streak_info()
        uniquewords = lang.get_unique_words()
        testwords = get_test_words(uniquewords)
        wordsdict = get_word_dict(testwords)
        form = TestForm()
        context = {
            'testwords': testwords, # list of random unique test words
            'wordsdict': wordsdict, # dict of words + definitions {word: {pronounciation, definition}}
            'form': form,
        }
    return render(request, template_name, context)



回答2:


I accomplished this two ways: one of which includes a file write and the other a model write. Since writing to models is obviously faster, I will showcase that:

In the view I think it's pretty straight forward. Here the start of this is that I am passing in the word dictionary to the form when I instantiate the form on a GET request form = TestForm(wordsdict). POST request data is not ever actually stored, just used to validate. So when I POST, I simply send in the POST data as normal. wordsdict is a dictionary composed of a {answer: [question, id]}

views.py

def language_test(request, username='johndoe', password=None):
    lang= Language(config('USER'), config('PASS'))
    streakinfo = lang.get_streak_info()
    context = {
        'username': username,
        'streakinfo': streakinfo,
    }
    template_name = 'tests/test.html'
    # if this is a POST request we need to process the form data
    if request.method == 'POST':
        # create a form instance and populate it with data from the saved answer dictionary:
        print('POSTING TEST RESULTS')
        form = TestForm(data=request.POST)
        # check whether it's valid:
        if form.is_valid():
            # process the data in form.cleaned_data as required
            print('PASSED')
            # redirect to a new URL:
            return redirect('main:success')
        else:  
            print('FAILED')
            if form.has_error:
                print('FORM ERROR')
            pass
    # if a GET (or any other method) we'll create a blank form
    else:
        print('GETTING NEW TEST')
        phrases = lang.get_known_phrases()
        testwords = get_test_words(phrases)
        wordsdict = get_word_dict(testwords)
        form = TestForm(wordsdict)
    context['form'] = form
    return render(request, template_name, context)

Straight forward again...

models.py

class TestAnswers(models.Model):
    phraseid = models.IntegerField(unique=True, blank=True, null=True)
    question = models.TextField(blank=True, null=True)
    answer = models.CharField(max_length=50, blank=True, null=True)

Here is where the magic happens. I'm super'ing the __init__ function of the inherited Form class. When the class is instantiated, it will evaluate the test_dict argument, which may or may not have been passed in by the view. If there is no test_dict, it must be a request for a new test, so I clear out the test model and create a new one with the randomly selected questions\answers passed in by the view. If there is no test_dict passed in, then it must be a post request, meaning I need to validate all the answers. Refer to the clean method for the form validation.

forms.py

class TestForm(forms.Form):
    """
    Student test form
    """    
    def __init__(self, test_dict=None, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._resource_path = os.path.join(settings.BASE_DIR, 'static/json')
        self._json_path = os.path.join(self._resource_path, 'answers.json')
        self._model = TestAnswers
        i = 0
        phraseid, answer, question = [], [], []
        if test_dict is not None:
        # A form get request should resolve new form data and
        # store it in the database for comparison later on
            # clear out the answers table
            self._model.objects.all().delete()
            # create a list of model objects to bulk insert
            records = []
            for item in test_dict:
                record = self._model(
                    phraseid=test_dict[item]['id'],
                    answer=item,
                    question=test_dict[item]['definition']
                )
                phraseid.append(test_dict[item]['id'])
                question.append(test_dict[item]['definition'])
                answer.append(item)
                records.append(record)
            if records:
                # Insert the records into the TestAnswers table
                self._model.objects.bulk_create(records)
            self.test_dict = test_dict

        else:
        # A form post request should check the form data against
        # what was established during the get request
            # Get all the objects in the test table
            records = self._model.objects.all()
            # Put all the object items into their respective lists
            for r in records:
                phraseid.append(r.phraseid)
                answer.append(r.answer)
                question.append(r.question)
        for i in range(len(question)):
            # Set the form fields
            field_name = 'testword' + str(phraseid[i])
            # Print the answers for debugging
            print('id: ' + str(phraseid[i]))
            print('question: ' + question[i])
            print('answer:' + answer[i])
            self.fields[field_name] = forms.CharField(label=question[i], max_length=100)
        self.question = question
        self.phraseid = phraseid
        self.answer = answer

    def clean(self):
        # print('CLEANING DATA')
        phraseid, answer, question = [], [], []
        context = {}
        i = 0
        records = self._model.objects.all()
        for r in records:
            phraseid.append(r.phraseid)
            answer.append(r.answer)
            question.append(r.question)
        # Get and check the results
        for i in range(len(self.cleaned_data)):
            field_name = 'testword' + str(phraseid[i])
            result = self.cleaned_data.get(field_name)
            if result != answer[i]:
                self.add_error(field_name, 'Incorrect')
            context[i] = question[i]
            i += 1
        return context



来源:https://stackoverflow.com/questions/54684678/manually-rendering-django-form-with-validation

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!