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