Package coprs :: Module forms
[hide private]
[frames] | no frames]

Source Code for Module coprs.forms

  1  import re 
  2  from six.moves.urllib.parse import urlparse 
  3   
  4  import flask 
  5  import wtforms 
  6  import json 
  7   
  8  from flask_wtf.file import FileAllowed, FileRequired, FileField 
  9   
 10  from flask.ext import wtf 
 11  from jinja2 import Markup 
 12   
 13  from coprs import constants 
 14  from coprs import helpers 
 15  from coprs import models 
 16  from coprs.logic.coprs_logic import CoprsLogic 
 17  from coprs.logic.users_logic import UsersLogic 
 18  from coprs.logic.modules_logic import ModulesLogic 
 19  from coprs.models import Package 
 20  from exceptions import UnknownSourceTypeException 
21 22 -def get_package_form_cls_by_source_type_text(source_type_text):
23 """ 24 Params 25 ------ 26 source_type_text : str 27 name of the source type (tito/mock/pypi/rubygems/upload) 28 29 Returns 30 ------- 31 BasePackageForm child 32 based on source_type_text input 33 """ 34 if source_type_text == 'git_and_tito': 35 return PackageFormTito 36 elif source_type_text == 'mock_scm': 37 return PackageFormMock 38 elif source_type_text == 'pypi': 39 return PackageFormPyPI 40 elif source_type_text == 'rubygems': 41 return PackageFormRubyGems 42 else: 43 raise UnknownSourceTypeException("Invalid source type")
44
45 46 -class MultiCheckboxField(wtforms.SelectMultipleField):
47 widget = wtforms.widgets.ListWidget(prefix_label=False) 48 option_widget = wtforms.widgets.CheckboxInput()
49
50 51 -class UrlListValidator(object):
52
53 - def __init__(self, message=None):
54 if not message: 55 message = ("A list of http[s] URLs separated by whitespace characters" 56 " is needed ('{0}' doesn't seem to be a valid URL).") 57 self.message = message
58
59 - def __call__(self, form, field):
60 urls = field.data.split() 61 for u in urls: 62 if not self.is_url(u): 63 raise wtforms.ValidationError(self.message.format(u))
64
65 - def is_url(self, url):
66 parsed = urlparse(url) 67 if not parsed.scheme.startswith("http"): 68 return False 69 if not parsed.netloc: 70 return False 71 return True
72
73 74 -class UrlRepoListValidator(UrlListValidator):
75 """ Allows also `repo://` schema"""
76 - def is_url(self, url):
77 parsed = urlparse(url) 78 if parsed.scheme not in ["http", "https", "copr"]: 79 return False 80 if not parsed.netloc: 81 return False 82 # copr://username/projectname 83 # ^^ schema ^^ netlock ^^ path 84 if parsed.scheme == "copr": 85 # check if projectname missed 86 path_split = parsed.path.split("/") 87 if len(path_split) < 2 or path_split[1] == "": 88 return False 89 90 return True
91
92 93 -class UrlSrpmListValidator(UrlListValidator):
94 - def __init__(self, message=None):
95 if not message: 96 message = ("URLs must end with .src.rpm" 97 " ('{0}' doesn't seem to be a valid SRPM URL).") 98 super(UrlSrpmListValidator, self).__init__(message)
99
100 - def is_url(self, url):
101 parsed = urlparse(url) 102 if not parsed.path.endswith((".src.rpm", ".nosrc.rpm")): 103 return False 104 return True
105
106 107 -class SrpmValidator(object):
108 - def __init__(self, message=None):
109 if not message: 110 message = "You can upload only .src.rpm and .nosrc.rpm files" 111 self.message = message
112
113 - def __call__(self, form, field):
114 filename = field.data.filename.lower() 115 if not filename.endswith((".src.rpm", ".nosrc.rpm")): 116 raise wtforms.ValidationError(self.message)
117
118 119 -class CoprUniqueNameValidator(object):
120
121 - def __init__(self, message=None, user=None, group=None):
122 if not message: 123 if group is None: 124 message = "You already have project named '{}'." 125 else: 126 message = "Group {} ".format(group) + "already have project named '{}'." 127 self.message = message 128 if not user: 129 user = flask.g.user 130 self.user = user 131 self.group = group
132
133 - def __call__(self, form, field):
134 if self.group: 135 existing = CoprsLogic.exists_for_group( 136 self.group, field.data).first() 137 else: 138 existing = CoprsLogic.exists_for_user( 139 self.user, field.data).first() 140 141 if existing and str(existing.id) != form.id.data: 142 raise wtforms.ValidationError(self.message.format(field.data))
143
144 145 -class NameNotNumberValidator(object):
146
147 - def __init__(self, message=None):
148 if not message: 149 message = "Project's name can not be just number." 150 self.message = message
151
152 - def __call__(self, form, field):
153 if field.data.isdigit(): 154 raise wtforms.ValidationError(self.message.format(field.data))
155
156 157 -class EmailOrURL(object):
158
159 - def __init__(self, message=None):
160 if not message: 161 message = "{} must be email address or URL" 162 self.message = message
163
164 - def __call__(self, form, field):
165 for validator in [wtforms.validators.Email(), wtforms.validators.URL()]: 166 try: 167 validator(form, field) 168 return True 169 except wtforms.ValidationError: 170 pass 171 raise wtforms.ValidationError(self.message.format(field.name.capitalize()))
172
173 174 -class StringListFilter(object):
175
176 - def __call__(self, value):
177 if not value: 178 return '' 179 # Replace every whitespace string with one newline 180 # Formats ideally for html form filling, use replace('\n', ' ') 181 # to get space-separated values or split() to get list 182 result = value.strip() 183 regex = re.compile(r"\s+") 184 return regex.sub(lambda x: '\n', result)
185
186 187 -class ValueToPermissionNumberFilter(object):
188
189 - def __call__(self, value):
190 if value: 191 return helpers.PermissionEnum("request") 192 return helpers.PermissionEnum("nothing")
193
194 195 -class CoprFormFactory(object):
196 197 @staticmethod
198 - def create_form_cls(mock_chroots=None, user=None, group=None):
199 class F(wtf.Form): 200 # also use id here, to be able to find out whether user 201 # is updating a copr if so, we don't want to shout 202 # that name already exists 203 id = wtforms.HiddenField() 204 group_id = wtforms.HiddenField() 205 206 name = wtforms.StringField( 207 "Name", 208 validators=[ 209 wtforms.validators.DataRequired(), 210 wtforms.validators.Regexp( 211 re.compile(r"^[\w.-]+$"), 212 message="Name must contain only letters," 213 "digits, underscores, dashes and dots."), 214 CoprUniqueNameValidator(user=user, group=group), 215 NameNotNumberValidator() 216 ]) 217 218 homepage = wtforms.StringField( 219 "Homepage", 220 validators=[ 221 wtforms.validators.Optional(), 222 wtforms.validators.URL()]) 223 224 contact = wtforms.StringField( 225 "Contact", 226 validators=[ 227 wtforms.validators.Optional(), 228 EmailOrURL()]) 229 230 description = wtforms.TextAreaField("Description") 231 232 instructions = wtforms.TextAreaField("Instructions") 233 234 repos = wtforms.TextAreaField( 235 "External Repositories", 236 validators=[UrlRepoListValidator()], 237 filters=[StringListFilter()]) 238 239 initial_pkgs = wtforms.TextAreaField( 240 "Initial packages to build", 241 validators=[ 242 UrlListValidator(), 243 UrlSrpmListValidator()], 244 filters=[StringListFilter()]) 245 246 disable_createrepo = wtforms.BooleanField(default=False) 247 build_enable_net = wtforms.BooleanField(default=False) 248 unlisted_on_hp = wtforms.BooleanField("Do not display this project on home page", default=False) 249 persistent = wtforms.BooleanField(default=False) 250 auto_prune = wtforms.BooleanField("If backend auto-prunning script should be run for this project", default=True) 251 252 @property 253 def selected_chroots(self): 254 selected = [] 255 for ch in self.chroots_list: 256 if getattr(self, ch).data: 257 selected.append(ch) 258 return selected
259 260 def validate(self): 261 if not super(F, self).validate(): 262 return False 263 264 if not self.validate_mock_chroots_not_empty(): 265 self.errors["chroots"] = ["At least one chroot must be selected"] 266 return False 267 return True
268 269 def validate_mock_chroots_not_empty(self): 270 have_any = False 271 for c in self.chroots_list: 272 if getattr(self, c).data: 273 have_any = True 274 return have_any 275 276 F.chroots_list = list(map(lambda x: x.name, 277 models.MockChroot.query.filter( 278 models.MockChroot.is_active == True 279 ).all())) 280 F.chroots_list.sort() 281 # sets of chroots according to how we should print them in columns 282 F.chroots_sets = {} 283 for ch in F.chroots_list: 284 checkbox_default = False 285 if mock_chroots and ch in map(lambda x: x.name, 286 mock_chroots): 287 checkbox_default = True 288 289 setattr(F, ch, wtforms.BooleanField(ch, default=checkbox_default)) 290 if ch[0] in F.chroots_sets: 291 F.chroots_sets[ch[0]].append(ch) 292 else: 293 F.chroots_sets[ch[0]] = [ch] 294 295 return F 296
297 298 -class CoprDeleteForm(wtf.Form):
299 verify = wtforms.TextField( 300 "Confirm deleting by typing 'yes'", 301 validators=[ 302 wtforms.validators.Required(), 303 wtforms.validators.Regexp( 304 r"^yes$", 305 message="Type 'yes' - without the quotes, lowercase.") 306 ])
307
308 309 # @TODO jkadlcik - rewrite via BaseBuildFormFactory after fe-dev-cloud is back online 310 -class BuildFormRebuildFactory(object):
311 @staticmethod
312 - def create_form_cls(active_chroots):
313 class F(wtf.Form): 314 @property 315 def selected_chroots(self): 316 selected = [] 317 for ch in self.chroots_list: 318 if getattr(self, ch).data: 319 selected.append(ch) 320 return selected
321 322 memory_reqs = wtforms.IntegerField( 323 "Memory requirements", 324 validators=[ 325 wtforms.validators.NumberRange( 326 min=constants.MIN_BUILD_MEMORY, 327 max=constants.MAX_BUILD_MEMORY)], 328 default=constants.DEFAULT_BUILD_MEMORY) 329 330 timeout = wtforms.IntegerField( 331 "Timeout", 332 validators=[ 333 wtforms.validators.NumberRange( 334 min=constants.MIN_BUILD_TIMEOUT, 335 max=constants.MAX_BUILD_TIMEOUT)], 336 default=constants.DEFAULT_BUILD_TIMEOUT) 337 338 enable_net = wtforms.BooleanField()
339 340 F.chroots_list = list(map(lambda x: x.name, active_chroots)) 341 F.chroots_list.sort() 342 F.chroots_sets = {} 343 for ch in F.chroots_list: 344 setattr(F, ch, wtforms.BooleanField(ch, default=True)) 345 if ch[0] in F.chroots_sets: 346 F.chroots_sets[ch[0]].append(ch) 347 else: 348 F.chroots_sets[ch[0]] = [ch] 349 350 return F 351
352 353 -class BasePackageForm(wtf.Form):
354 package_name = wtforms.StringField( 355 "Package name", 356 validators=[wtforms.validators.DataRequired()]) 357 webhook_rebuild = wtforms.BooleanField(default=False)
358
359 360 -class PackageFormTito(BasePackageForm):
361 git_url = wtforms.StringField( 362 "Git URL", 363 validators=[ 364 wtforms.validators.DataRequired(), 365 wtforms.validators.URL()]) 366 367 git_directory = wtforms.StringField( 368 "Git Directory", 369 validators=[ 370 wtforms.validators.Optional()]) 371 372 git_branch = wtforms.StringField( 373 "Git Branch", 374 validators=[ 375 wtforms.validators.Optional()]) 376 377 tito_test = wtforms.BooleanField(default=False) 378 379 @property
380 - def source_json(self):
381 return json.dumps({ 382 "git_url": self.git_url.data, 383 "git_branch": self.git_branch.data, 384 "git_dir": self.git_directory.data, 385 "tito_test": self.tito_test.data 386 })
387
388 389 -class PackageFormMock(BasePackageForm):
390 scm_type = wtforms.SelectField( 391 "SCM Type", 392 choices=[("git", "Git"), ("svn", "SVN")]) 393 394 scm_url = wtforms.StringField( 395 "SCM URL", 396 validators=[ 397 wtforms.validators.DataRequired(), 398 wtforms.validators.URL()]) 399 400 scm_branch = wtforms.StringField( 401 "Git Branch", 402 validators=[ 403 wtforms.validators.Optional()]) 404 405 spec = wtforms.StringField( 406 "Spec File", 407 validators=[ 408 wtforms.validators.Regexp( 409 "^.+\.spec$", 410 message="RPM spec file must end with .spec")]) 411 412 @property
413 - def source_json(self):
414 return json.dumps({ 415 "scm_type": self.scm_type.data, 416 "scm_url": self.scm_url.data, 417 "scm_branch": self.scm_branch.data, 418 "spec": self.spec.data 419 })
420
421 422 -class PackageFormPyPI(BasePackageForm):
423 pypi_package_name = wtforms.StringField( 424 "PyPI package name", 425 validators=[wtforms.validators.DataRequired()]) 426 427 pypi_package_version = wtforms.StringField( 428 "PyPI package version", 429 validators=[ 430 wtforms.validators.Optional(), 431 ]) 432 433 python_versions = MultiCheckboxField( 434 'Build for Python', 435 choices=[ 436 ('3', 'python3'), 437 ('2', 'python2') 438 ], 439 default=['3', '2']) 440 441 @property
442 - def source_json(self):
443 return json.dumps({ 444 "pypi_package_name": self.pypi_package_name.data, 445 "pypi_package_version": self.pypi_package_version.data, 446 "python_versions": self.python_versions.data 447 })
448
449 450 -class PackageFormRubyGems(BasePackageForm):
451 gem_name = wtforms.StringField( 452 "Gem Name", 453 validators=[wtforms.validators.DataRequired()]) 454 455 @property
456 - def source_json(self):
457 return json.dumps({ 458 "gem_name": self.gem_name.data 459 })
460
461 462 -class PackageFormDistGit(BasePackageForm):
463 clone_url = wtforms.StringField( 464 "Clone Url", 465 validators=[wtforms.validators.DataRequired()]) 466 467 branch = wtforms.StringField( 468 "Branch", 469 validators=[wtforms.validators.Optional()]) 470 471 @property
472 - def source_json(self):
473 return json.dumps({ 474 "clone_url": self.clone_url.data, 475 "branch": self.branch.data 476 })
477
478 479 -class BaseBuildFormFactory(object):
480 - def __new__(cls, active_chroots, form):
481 class F(form): 482 @property 483 def selected_chroots(self): 484 selected = [] 485 for ch in self.chroots_list: 486 if getattr(self, ch).data: 487 selected.append(ch) 488 return selected
489 490 F.memory_reqs = wtforms.IntegerField( 491 "Memory requirements", 492 validators=[ 493 wtforms.validators.Optional(), 494 wtforms.validators.NumberRange( 495 min=constants.MIN_BUILD_MEMORY, 496 max=constants.MAX_BUILD_MEMORY)], 497 default=constants.DEFAULT_BUILD_MEMORY) 498 499 F.timeout = wtforms.IntegerField( 500 "Timeout", 501 validators=[ 502 wtforms.validators.Optional(), 503 wtforms.validators.NumberRange( 504 min=constants.MIN_BUILD_TIMEOUT, 505 max=constants.MAX_BUILD_TIMEOUT)], 506 default=constants.DEFAULT_BUILD_TIMEOUT) 507 508 509 F.enable_net = wtforms.BooleanField() 510 F.background = wtforms.BooleanField(default=False) 511 F.package_name = wtforms.StringField() 512 513 F.chroots_list = map(lambda x: x.name, active_chroots) 514 F.chroots_list.sort() 515 F.chroots_sets = {} 516 for ch in F.chroots_list: 517 setattr(F, ch, wtforms.BooleanField(ch, default=True)) 518 if ch[0] in F.chroots_sets: 519 F.chroots_sets[ch[0]].append(ch) 520 else: 521 F.chroots_sets[ch[0]] = [ch] 522 return F 523
524 525 -class BuildFormTitoFactory(object):
526 - def __new__(cls, active_chroots):
528
529 530 -class BuildFormMockFactory(object):
531 - def __new__(cls, active_chroots):
533
534 535 -class BuildFormPyPIFactory(object):
536 - def __new__(cls, active_chroots):
538
539 540 -class BuildFormRubyGemsFactory(object):
541 - def __new__(cls, active_chroots):
543
544 545 -class BuildFormDistGitFactory(object):
546 - def __new__(cls, active_chroots):
548
549 550 -class BuildFormUploadFactory(object):
551 - def __new__(cls, active_chroots):
552 form = BaseBuildFormFactory(active_chroots, wtf.Form) 553 form.pkgs = FileField('srpm', validators=[ 554 FileRequired(), 555 SrpmValidator()]) 556 return form
557
558 559 -class BuildFormUrlFactory(object):
560 - def __new__(cls, active_chroots):
561 form = BaseBuildFormFactory(active_chroots, wtf.Form) 562 form.pkgs = wtforms.TextAreaField( 563 "Pkgs", 564 validators=[ 565 wtforms.validators.DataRequired(message="URLs to packages are required"), 566 UrlListValidator(), 567 UrlSrpmListValidator()], 568 filters=[StringListFilter()]) 569 return form
570
571 572 -class ModuleFormUploadFactory(wtf.Form):
573 modulemd = FileField("modulemd", validators=[ 574 FileRequired(), 575 # @TODO Validate modulemd.yaml file 576 ])
577
578 579 -class ChrootForm(wtf.Form):
580 581 """ 582 Validator for editing chroots in project 583 (adding packages to minimal chroot) 584 """ 585 586 buildroot_pkgs = wtforms.TextField( 587 "Packages") 588 589 repos = wtforms.TextAreaField('Repos', 590 validators=[UrlRepoListValidator(), 591 wtforms.validators.Optional()], 592 filters=[StringListFilter()]) 593 594 module_md = FileField("module_md") 595 596 comps = FileField("comps_xml")
597
598 599 -class CoprLegalFlagForm(wtf.Form):
600 comment = wtforms.TextAreaField("Comment")
601
602 603 -class PermissionsApplierFormFactory(object):
604 605 @staticmethod
606 - def create_form_cls(permission=None):
607 class F(wtf.Form): 608 pass
609 610 builder_default = False 611 admin_default = False 612 613 if permission: 614 if permission.copr_builder != helpers.PermissionEnum("nothing"): 615 builder_default = True 616 if permission.copr_admin != helpers.PermissionEnum("nothing"): 617 admin_default = True 618 619 setattr(F, "copr_builder", 620 wtforms.BooleanField( 621 default=builder_default, 622 filters=[ValueToPermissionNumberFilter()])) 623 624 setattr(F, "copr_admin", 625 wtforms.BooleanField( 626 default=admin_default, 627 filters=[ValueToPermissionNumberFilter()])) 628 629 return F
630
631 632 -class PermissionsFormFactory(object):
633 634 """Creates a dynamic form for given set of copr permissions""" 635 @staticmethod
636 - def create_form_cls(permissions):
637 class F(wtf.Form): 638 pass
639 640 for perm in permissions: 641 builder_choices = helpers.PermissionEnum.choices_list() 642 admin_choices = helpers.PermissionEnum.choices_list() 643 644 builder_default = perm.copr_builder 645 admin_default = perm.copr_admin 646 647 setattr(F, "copr_builder_{0}".format(perm.user.id), 648 wtforms.SelectField( 649 choices=builder_choices, 650 default=builder_default, 651 coerce=int)) 652 653 setattr(F, "copr_admin_{0}".format(perm.user.id), 654 wtforms.SelectField( 655 choices=admin_choices, 656 default=admin_default, 657 coerce=int)) 658 659 return F
660
661 662 -class CoprModifyForm(wtf.Form):
663 description = wtforms.TextAreaField('Description', 664 validators=[wtforms.validators.Optional()]) 665 666 instructions = wtforms.TextAreaField('Instructions', 667 validators=[wtforms.validators.Optional()]) 668 669 repos = wtforms.TextAreaField('Repos', 670 validators=[UrlRepoListValidator(), 671 wtforms.validators.Optional()], 672 filters=[StringListFilter()]) 673 674 disable_createrepo = wtforms.BooleanField(validators=[wtforms.validators.Optional()]) 675 unlisted_on_hp = wtforms.BooleanField(validators=[wtforms.validators.Optional()]) 676 build_enable_net = wtforms.BooleanField(validators=[wtforms.validators.Optional()]) 677 auto_prune = wtforms.BooleanField(validators=[wtforms.validators.Optional()])
678
679 680 -class CoprForkFormFactory(object):
681 @staticmethod
682 - def create_form_cls(copr, user, groups):
683 class F(wtf.Form): 684 source = wtforms.StringField( 685 "Source", 686 default=copr.full_name) 687 688 owner = wtforms.SelectField( 689 "Fork owner", 690 choices=[(user.name, user.name)] + [(g.at_name, g.at_name) for g in groups], 691 default=user.name, 692 validators=[wtforms.validators.DataRequired()]) 693 694 name = wtforms.StringField( 695 "Fork name", 696 default=copr.name, 697 validators=[wtforms.validators.DataRequired()]) 698 699 confirm = wtforms.BooleanField( 700 "Confirm", 701 default=False)
702 return F
703
704 705 -class ModifyChrootForm(wtf.Form):
706 buildroot_pkgs = wtforms.TextField('Additional packages to be always present in minimal buildroot') 707 repos = wtforms.TextAreaField('Additional repos to be used for builds in chroot', 708 validators=[UrlRepoListValidator(), 709 wtforms.validators.Optional()], 710 filters=[StringListFilter()]) 711 upload_comps = FileField("Upload comps.xml") 712 delete_comps = wtforms.BooleanField("Delete comps.xml")
713
714 715 -class AdminPlaygroundForm(wtf.Form):
716 playground = wtforms.BooleanField("Playground")
717
718 719 -class AdminPlaygroundSearchForm(wtf.Form):
720 project = wtforms.TextField("Project")
721
722 723 -class GroupUniqueNameValidator(object):
724
725 - def __init__(self, message=None):
726 if not message: 727 message = "Group with the alias '{}' already exists." 728 self.message = message
729
730 - def __call__(self, form, field):
731 if UsersLogic.group_alias_exists(field.data): 732 raise wtforms.ValidationError(self.message.format(field.data))
733
734 735 -class ActivateFasGroupForm(wtf.Form):
736 737 name = wtforms.StringField( 738 validators=[ 739 wtforms.validators.Regexp( 740 re.compile(r"^[\w.-]+$"), 741 message="Name must contain only letters," 742 "digits, underscores, dashes and dots."), 743 GroupUniqueNameValidator() 744 ] 745 )
746
747 748 -class CreateModuleForm(wtf.Form):
749 name = wtforms.StringField("Name") 750 stream = wtforms.StringField("Stream") 751 version = wtforms.IntegerField("Version") 752 filter = wtforms.FieldList(wtforms.StringField("Package Filter")) 753 api = wtforms.FieldList(wtforms.StringField("Module API")) 754 profile_names = wtforms.FieldList(wtforms.StringField("Install Profiles"), min_entries=2) 755 profile_pkgs = wtforms.FieldList(wtforms.FieldList(wtforms.StringField("Install Profiles")), min_entries=2) 756
757 - def __init__(self, copr=None, *args, **kwargs):
758 self.copr = copr 759 super(CreateModuleForm, self).__init__(*args, **kwargs)
760
761 - def validate(self):
762 if not wtf.Form.validate(self): 763 return False 764 765 module = ModulesLogic.get_by_nsv(self.copr, self.name.data, self.stream.data, self.version.data).first() 766 if module: 767 self.errors["nsv"] = [Markup("Module <a href='{}'>{}</a> already exists".format( 768 helpers.copr_url("coprs_ns.copr_module", module.copr, id=module.id), module.full_name))] 769 return False 770 771 # Profile names should be unique 772 names = filter(None, self.profile_names.data) 773 if len(set(names)) < len(names): 774 self.errors["profiles"] = ["Profile names must be unique"] 775 return False 776 777 # WORKAROUND 778 # profile_pkgs are somehow sorted so if I fill profile_name in the first box and 779 # profile_pkgs in seconds box, it is sorted and validated correctly 780 for i in range(0, len(self.profile_names.data)): 781 # If profile name is not set, then there should not be any packages in this profile 782 if not flask.request.form["profile_names-{}".format(i)]: 783 if [j for j in range(0, len(self.profile_names)) if "profile_pkgs-{}-{}".format(i, j) in flask.request.form]: 784 self.errors["profiles"] = ["Missing profile name"] 785 return False 786 return True
787
788 789 -class ModuleRepo(wtf.Form):
790 owner = wtforms.StringField("Owner Name", validators=[wtforms.validators.DataRequired()]) 791 copr = wtforms.StringField("Copr Name", validators=[wtforms.validators.DataRequired()]) 792 name = wtforms.StringField("Name", validators=[wtforms.validators.DataRequired()]) 793 stream = wtforms.StringField("Stream", validators=[wtforms.validators.DataRequired()]) 794 version = wtforms.IntegerField("Version", validators=[wtforms.validators.DataRequired()]) 795 arch = wtforms.StringField("Arch", validators=[wtforms.validators.DataRequired()])
796