├── .idea ├── .name ├── encodings.xml ├── misc.xml ├── modules.xml └── online_compiler.iml ├── LICENSE ├── README.md ├── compiler ├── CompilerUtils.py ├── __init__.py ├── admin.py ├── apps.py ├── forms.py ├── migrations │ └── __init__.py ├── models.py ├── tests.py ├── urls.py └── views.py ├── manage.py ├── online_compiler ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py └── templates ├── OutputView.html ├── generic_error.html └── test-compiler.html /.idea/.name: -------------------------------------------------------------------------------- 1 | online_compiler -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/online_compiler.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 26 | 27 | 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ravi Teja 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # python-online-compiler 2 | A simple Django web application that can be used to run python code online 3 | -------------------------------------------------------------------------------- /compiler/CompilerUtils.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | import subprocess 3 | import random 4 | import os 5 | 6 | 7 | class Language(Enum): 8 | PYTHON = 1 9 | JAVA = 2 10 | 11 | 12 | # The ExecutionStatus class is an enum, one of which will be returned by the execute method 13 | class ExecutionStatus(Enum): 14 | NYR = 0 # Still Executing .... may take a few more time to get results 15 | ACC = 1 # Executed successfully, Solution correct, accepted 16 | WRA = 2 # Executed successfully, Solution wrong, rejected 17 | TLE = 3 # Executed, but exceeded time limit 18 | COE = 4 # Compilation failed 19 | RTE = 5 # Error encountered during Execution 20 | INE = 6 # Internal error has occurred, prompt user to try again! ( we'll be screwed if this happens often) 21 | 22 | 23 | class TestCase: 24 | input_data = None 25 | output_data = None 26 | 27 | def __init__(self, input_data, expected_output): 28 | self.input_data = input_data 29 | self.output_data = expected_output 30 | 31 | def get_input(self): 32 | return self.input_data 33 | 34 | def get_output(self): 35 | return self.output_data 36 | 37 | 38 | def generate_test_cases(input_string, output_string): 39 | test_cases = [] 40 | inputs = [x.strip() for x in input_string.split(sep='$END')] 41 | outputs = [x.strip() for x in output_string.split(sep='$END')] 42 | if len(inputs) is not len(outputs): 43 | return None # need a better way :( 44 | else: 45 | # removing last blank space (if any) generated due to split function 46 | if len(inputs[len(inputs)-1]) == 0: 47 | del inputs[len(inputs)-1] 48 | del outputs[len(outputs)-1] 49 | for i in range(len(inputs)): 50 | test_cases.append(TestCase(inputs[i], outputs[i])) 51 | return test_cases 52 | 53 | 54 | def generate_rand_name(length): 55 | generated = "" 56 | for i in range(length): 57 | base = 97 if random.randint(0, 1) == 0 else 65 58 | offset = random.randint(0, 25) 59 | generated += chr(base+offset) 60 | return generated 61 | 62 | 63 | class Compiler: 64 | exec_status = None # exec_status:None denotes that the program has'nt been executed yet (hasExecuted=False) 65 | code = None 66 | template = None 67 | test_cases = None 68 | outputs = None 69 | errors = None 70 | failed_test_cases = None # once execute() is called, this value will be set 71 | language = None 72 | filename = None 73 | hasErrors = False 74 | hasExecuted = False 75 | hasFile = False 76 | maxExecTime = 5 # [unit: seconds] Default value, can be overridden 77 | 78 | def add_test_case(self, test_case): 79 | print("** Test case added **") 80 | if isinstance(test_case, TestCase): 81 | if self.test_cases is None: 82 | self.test_cases = [] 83 | self.test_cases.append(test_case) 84 | else: 85 | raise ValueError("Trying to add Invalid test case!") 86 | return 87 | 88 | def get_num_test_cases(self): 89 | if self.test_cases is None: 90 | return 0 91 | else: 92 | return len(self.test_cases) 93 | 94 | def get_num_failed_test_cases(self): 95 | return self.failed_test_cases 96 | 97 | def set_language(self, l): 98 | if isinstance(l, Language): 99 | self.language = l 100 | else: 101 | self.language = None 102 | raise ValueError("Invalid language") 103 | 104 | def set_code(self, code): 105 | self.code = code 106 | return 107 | 108 | def set_template(self, template): 109 | if template is not None: 110 | self.template = template + "\r\n" 111 | return 112 | 113 | def set_max_exec_time(self, time_in_seconds): 114 | self.maxExecTime = time_in_seconds 115 | return 116 | 117 | # returns a list of outputs according to the respective test cases 118 | def get_output(self): 119 | if self.hasExecuted: 120 | return self.outputs 121 | else: 122 | return None 123 | 124 | # returns a list of errors according to the respective test cases 125 | def get_errors(self): 126 | if self.hasErrors: 127 | return self.errors 128 | else: 129 | return None 130 | 131 | def contains_errors(self): 132 | return self.hasErrors 133 | 134 | def generate_code_file(self): 135 | self.filename = generate_rand_name(10) 136 | if self.filename is None: 137 | print("*** ERROR : Filename cannot be generated! ***") 138 | if self.template is not None: 139 | complete_code = self.template + "\r\n" + self.code 140 | else: 141 | complete_code = self.code+"\r\n" 142 | file_handle = open(self.filename, "w") 143 | file_handle.write(complete_code) 144 | file_handle.flush() 145 | file_handle.close() 146 | 147 | def delete_code_file(self): 148 | if self.filename is None: 149 | print("*** ERROR: filename NONE ***") 150 | os.remove(self.filename) 151 | self.hasFile = False 152 | self.filename = None 153 | 154 | def compare_outputs(self): 155 | index = 0 156 | values = [] 157 | for test_case in self.test_cases: 158 | expected_output = test_case.get_output() 159 | actual_output = self.outputs[index].strip() 160 | 161 | # Debug ONLY.......................... 162 | print("## len(self.outputs) = "+str(len(self.outputs))) 163 | print("## index = "+str(index)) 164 | print("# EX: "+expected_output) 165 | print("# len : "+str(len(str(expected_output)))) 166 | print("# AC: "+actual_output) 167 | print("# len : "+str(len(str(actual_output)))) 168 | print("# Comparison : "+str(expected_output == actual_output)) 169 | print("\n") 170 | # Debug ONLY............................ 171 | 172 | values.append(expected_output == actual_output) 173 | index += 1 174 | 175 | # Debug ONLY ..................... 176 | print("Values .... ") 177 | for v in values: 178 | print(str(v)) 179 | # Debug ONLY ..................... 180 | 181 | return values 182 | 183 | def execute(self): 184 | self.exec_status = ExecutionStatus.NYR 185 | 186 | if self.language is not None: 187 | 188 | if not self.hasFile or self.filename is None: 189 | self.generate_code_file() 190 | 191 | if self.language == Language.PYTHON: 192 | command = ["python", self.filename] 193 | 194 | if self.outputs is None: 195 | self.outputs = [] 196 | 197 | if self.errors is None: 198 | self.errors = [] 199 | 200 | print("Test cases # : "+str(len(self.test_cases))) 201 | for test_case in self.test_cases: 202 | process = subprocess.Popen(command, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) 203 | 204 | try: 205 | o, e = process.communicate(str(test_case.get_input()).encode('utf-8'), timeout=self.maxExecTime) 206 | self.outputs.append(o.decode('utf-8')) 207 | 208 | if len(e) != 0: 209 | self.errors.append(e.decode('utf-8')) 210 | self.hasErrors = True 211 | else: 212 | self.errors.append(None) 213 | 214 | self.hasExecuted = True 215 | 216 | except subprocess.TimeoutExpired: 217 | print("*** TIMEOUT, killing process... ***") 218 | if process is not None: 219 | process.kill() 220 | self.hasExecuted = False 221 | self.exec_status = ExecutionStatus.TLE 222 | break 223 | 224 | if self.hasExecuted: 225 | comparisons = self.compare_outputs() 226 | if False in comparisons: 227 | self.exec_status = ExecutionStatus.WRA 228 | self.failed_test_cases = comparisons.count(False) 229 | else: 230 | self.exec_status = ExecutionStatus.ACC 231 | self.failed_test_cases = 0 232 | else: 233 | print("*** Error : Unknown Programming language Selected ****") 234 | self.exec_status = ExecutionStatus.INE 235 | else: 236 | print("*** Error : No Programming Language Selected ***") 237 | self.exec_status = ExecutionStatus.INE 238 | return self.exec_status 239 | -------------------------------------------------------------------------------- /compiler/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyberpirate92/python-online-compiler/79134c5da815f5d24c9a3e12fac945e31aee9cf9/compiler/__init__.py -------------------------------------------------------------------------------- /compiler/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /compiler/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CompilerConfig(AppConfig): 5 | name = 'compiler' 6 | -------------------------------------------------------------------------------- /compiler/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.forms import Form 3 | 4 | languages = [(1,"python")] 5 | languages = [(1,"python")] 6 | 7 | class CodeExecutorForm(Form): 8 | has_template = forms.BooleanField(required=False,label='Enable Template?') 9 | template = forms.CharField(widget=forms.Textarea,label='Template') 10 | code = forms.CharField(widget=forms.Textarea,label='Code') 11 | input = forms.CharField(widget=forms.Textarea,label='Input') 12 | output = forms.CharField(widget=forms.Textarea,label='Output') 13 | language=forms.ChoiceField(choices=languages,label='Language') 14 | -------------------------------------------------------------------------------- /compiler/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyberpirate92/python-online-compiler/79134c5da815f5d24c9a3e12fac945e31aee9cf9/compiler/migrations/__init__.py -------------------------------------------------------------------------------- /compiler/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. -------------------------------------------------------------------------------- /compiler/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /compiler/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | from . import views 3 | 4 | urlpatterns = [ 5 | url(r'^compiler$',views.testpage), 6 | ] -------------------------------------------------------------------------------- /compiler/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.http import HttpResponse 3 | from . import forms 4 | from . import CompilerUtils 5 | from .CompilerUtils import Compiler, Language 6 | 7 | 8 | # Create your views here. 9 | def testpage(request): 10 | template_data = {} 11 | if request.method == 'POST': 12 | form = forms.CodeExecutorForm(request.POST) 13 | if form.is_valid(): 14 | executor = Compiler() 15 | code = form.cleaned_data['code'] 16 | input_data = form.cleaned_data['input'] 17 | expected_output = form.cleaned_data['output'] 18 | test_cases = CompilerUtils.generate_test_cases(input_data, expected_output) 19 | for test_case in test_cases: 20 | executor.add_test_case(test_case) 21 | lan = Language(int(form.cleaned_data['language'])) 22 | has_template = form.cleaned_data['has_template'] 23 | if has_template: 24 | code_template = form.cleaned_data['template'] 25 | if len(input_data) == 0 or input_data is None: 26 | template_data['error'] = "Invalid code" 27 | return render(request, 'generic_error.html', template_data) 28 | else: 29 | if lan == Language.PYTHON: 30 | executor.set_code(code) 31 | executor.set_language(lan) 32 | if has_template: 33 | executor.set_template(code_template) 34 | execution_result = executor.execute() 35 | template_data['result'] = execution_result.name 36 | template_data['test_cases_total'] = executor.get_num_test_cases() 37 | if executor.get_num_failed_test_cases() is not None: 38 | template_data['test_cases_passed'] = executor.get_num_test_cases() - executor.get_num_failed_test_cases() 39 | executor.delete_code_file() 40 | if executor.hasExecuted: 41 | checked_values = executor.compare_outputs() 42 | display_data = [] 43 | outputs = executor.get_output() 44 | errors = executor.get_errors() 45 | for i in range(len(outputs)): 46 | if executor.hasErrors: 47 | e = errors[i] 48 | else: 49 | e = "No errors!" 50 | temp_tuple = (i+1, checked_values[i], outputs[i], e) 51 | display_data.append(temp_tuple) 52 | template_data['display_data'] = display_data 53 | return render(request, 'OutputView.html', template_data) 54 | else: 55 | return render(request, 'generic_error.html', {'error': 'Sorry! Execution failed'}) 56 | else: 57 | return HttpResponse("Cannot sanitize form data") 58 | else: 59 | form = forms.CodeExecutorForm() 60 | template_data['form'] = form 61 | return render(request, 'test-compiler.html', template_data) -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "online_compiler.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /online_compiler/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cyberpirate92/python-online-compiler/79134c5da815f5d24c9a3e12fac945e31aee9cf9/online_compiler/__init__.py -------------------------------------------------------------------------------- /online_compiler/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for online_compiler project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.9.1. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.9/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.9/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'l@l&v=ox4!(j*a7g2#efh3o35^epyh^$uq6-@#s521ra@(=$if' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'front', 35 | 'django.contrib.admin', 36 | 'django.contrib.auth', 37 | 'django.contrib.contenttypes', 38 | 'django.contrib.sessions', 39 | 'django.contrib.messages', 40 | 'django.contrib.staticfiles', 41 | 'debug_toolbar', 42 | ] 43 | 44 | MIDDLEWARE_CLASSES = [ 45 | 'django.middleware.security.SecurityMiddleware', 46 | 'django.contrib.sessions.middleware.SessionMiddleware', 47 | 'django.middleware.common.CommonMiddleware', 48 | 'django.middleware.csrf.CsrfViewMiddleware', 49 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 50 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 51 | 'django.contrib.messages.middleware.MessageMiddleware', 52 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 53 | ] 54 | 55 | ROOT_URLCONF = 'online_compiler.urls' 56 | 57 | TEMPLATES = [ 58 | { 59 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 60 | 'DIRS': [os.path.join(BASE_DIR, 'templates')] 61 | , 62 | 'APP_DIRS': True, 63 | 'OPTIONS': { 64 | 'context_processors': [ 65 | 'django.template.context_processors.debug', 66 | 'django.template.context_processors.request', 67 | 'django.contrib.auth.context_processors.auth', 68 | 'django.contrib.messages.context_processors.messages', 69 | ], 70 | }, 71 | }, 72 | ] 73 | 74 | WSGI_APPLICATION = 'online_compiler.wsgi.application' 75 | 76 | 77 | # Database 78 | # https://docs.djangoproject.com/en/1.9/ref/settings/#databases 79 | 80 | DATABASES = { 81 | 'default': { 82 | 'ENGINE': 'django.db.backends.postgresql', 83 | 'NAME': 'compiler_test', 84 | 'USER': 'postgres', 85 | 'PASSWORD': '12345', 86 | 'HOST': '127.0.0.1', 87 | 'PORT': '5432', 88 | } 89 | } 90 | 91 | 92 | # Password validation 93 | # https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators 94 | 95 | AUTH_PASSWORD_VALIDATORS = [ 96 | { 97 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 98 | }, 99 | { 100 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 101 | }, 102 | { 103 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 104 | }, 105 | { 106 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 107 | }, 108 | ] 109 | 110 | 111 | # Internationalization 112 | # https://docs.djangoproject.com/en/1.9/topics/i18n/ 113 | 114 | LANGUAGE_CODE = 'en-us' 115 | 116 | TIME_ZONE = 'UTC' 117 | 118 | USE_I18N = True 119 | 120 | USE_L10N = True 121 | 122 | USE_TZ = True 123 | 124 | 125 | # Static files (CSS, JavaScript, Images) 126 | # https://docs.djangoproject.com/en/1.9/howto/static-files/ 127 | 128 | STATIC_URL = '/static/' 129 | -------------------------------------------------------------------------------- /online_compiler/urls.py: -------------------------------------------------------------------------------- 1 | """online_compiler URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.9/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.conf.urls import url,include 17 | from django.contrib import admin 18 | 19 | urlpatterns = [ 20 | url(r'^front-edit/', include('front.urls')), 21 | url(r'^test/',include('compiler.urls')), 22 | url(r'^admin/', admin.site.urls), 23 | ] 24 | -------------------------------------------------------------------------------- /online_compiler/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for online_compiler project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "online_compiler.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /templates/OutputView.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Result 6 | 69 | 70 | 71 |
72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 |
Status Test Cases
Total Passed
{{ result }} {{ test_cases_total }} {{ test_cases_passed }}
87 |
88 | {% for i,status,output,error in display_data %} 89 | {% if status == True %} 90 |
91 |
92 |

Testcase {{ i }} success

93 |
94 | {% else %} 95 |
96 |
97 |

Testcase {{ i }} fail

98 |
99 | {% endif %} 100 | 101 |
102 |

Output:

103 |

{{ output | safe }}
104 |

Errors:

105 |

{{ error | safe }}
106 |
107 |
108 | {% endfor %} 109 | 110 | -------------------------------------------------------------------------------- /templates/generic_error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ERROR 6 | 7 | 8 |

{{ error }}

9 | 10 | -------------------------------------------------------------------------------- /templates/test-compiler.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Title 7 | 30 | 31 | 32 |
33 |

Online Compiler

34 |
{% csrf_token %} 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 71 | 72 |
{{ form.has_template.label }} {{ form.has_template }} {{ form.has_template.errors }}
{{ form.language.label }}{{ form.language }}{{ form.language.errors }}
{{ form.template.label }}{{ form.template }}{{ form.code.label }}
{{ form.code }}
{{ form.template.errors }}{{ form.code.errors }}
{{ form.input.label }}{{ form.input }}{{ form.output.label }}{{ form.output }}
{{ form.input.errors }}{{ form.output.errors }}
68 | 69 | 70 |
73 |
74 |
75 | 76 | --------------------------------------------------------------------------------