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

Source Code for Module coprs.models

  1  import datetime 
  2   
  3  from sqlalchemy.ext.associationproxy import association_proxy 
  4  from libravatar import libravatar_url 
  5   
  6  from coprs import constants 
  7  from coprs import db 
  8  from coprs import helpers 
9 10 11 -class User(db.Model, helpers.Serializer):
12 13 """ 14 Represents user of the copr frontend 15 """ 16 17 id = db.Column(db.Integer, primary_key=True) 18 # openid_name for fas, e.g. http://bkabrda.id.fedoraproject.org/ 19 openid_name = db.Column(db.String(100), nullable=False) 20 # just mail :) 21 mail = db.Column(db.String(150), nullable=False) 22 # just timezone ;) 23 timezone = db.Column(db.String(50), nullable=True) 24 # is this user proven? proven users can modify builder memory and 25 # timeout for single builds 26 proven = db.Column(db.Boolean, default=False) 27 # is this user admin of the system? 28 admin = db.Column(db.Boolean, default=False) 29 # stuff for the cli interface 30 api_login = db.Column(db.String(40), nullable=False, default="abc") 31 api_token = db.Column(db.String(40), nullable=False, default="abc") 32 api_token_expiration = db.Column( 33 db.Date, nullable=False, default=datetime.date(2000, 1, 1)) 34 35 @property
36 - def name(self):
37 """ 38 Return the short username of the user, e.g. bkabrda 39 """ 40 41 return self.openid_name.replace( 42 ".id.fedoraproject.org/", "").replace("http://", "")
43
44 - def permissions_for_copr(self, copr):
45 """ 46 Get permissions of this user for the given copr. 47 Caches the permission during one request, 48 so use this if you access them multiple times 49 """ 50 51 if not hasattr(self, "_permissions_for_copr"): 52 self._permissions_for_copr = {} 53 if not copr.name in self._permissions_for_copr: 54 self._permissions_for_copr[copr.name] = (CoprPermission.query 55 .filter_by(user=self).filter_by(copr=copr).first()) 56 return self._permissions_for_copr[copr.name]
57
58 - def can_build_in(self, copr):
59 """ 60 Determine if this user can build in the given copr. 61 """ 62 63 can_build = False 64 if copr.owner == self: 65 can_build = True 66 if (self.permissions_for_copr(copr) and 67 self.permissions_for_copr(copr).copr_builder == 68 helpers.PermissionEnum("approved")): 69 70 can_build = True 71 72 return can_build
73
74 - def can_edit(self, copr):
75 """ 76 Determine if this user can edit the given copr. 77 """ 78 79 can_edit = False 80 if copr.owner == self: 81 can_edit = True 82 if (self.permissions_for_copr(copr) and 83 self.permissions_for_copr(copr).copr_admin == 84 helpers.PermissionEnum("approved")): 85 86 can_edit = True 87 88 return can_edit
89 90 @classmethod
91 - def openidize_name(cls, name):
92 """ 93 Create proper openid_name from short name. 94 95 >>> user.openid_name == User.openidize_name(user.name) 96 True 97 """ 98 99 return "http://{0}.id.fedoraproject.org/".format(name)
100 101 @property
102 - def serializable_attributes(self):
103 # enumerate here to prevent exposing credentials 104 return ["id", "name"]
105 106 @property
107 - def coprs_count(self):
108 """ 109 Get number of coprs for this user. 110 """ 111 112 return (Copr.query.filter_by(owner=self). 113 filter_by(deleted=False). 114 count())
115 116 @property
117 - def gravatar_url(self):
118 """ 119 Return url to libravatar image. 120 """ 121 122 try: 123 return libravatar_url(email=self.mail, https=True) 124 except IOError: 125 return ""
126
127 128 -class Copr(db.Model, helpers.Serializer):
129 130 """ 131 Represents a single copr (private repo with builds, mock chroots, etc.). 132 """ 133 134 id = db.Column(db.Integer, primary_key=True) 135 # name of the copr, no fancy chars (checked by forms) 136 name = db.Column(db.String(100), nullable=False) 137 # string containing urls of additional repos (separated by space) 138 # that this copr will pull dependencies from 139 repos = db.Column(db.Text) 140 # time of creation as returned by int(time.time()) 141 created_on = db.Column(db.Integer) 142 # description and instructions given by copr owner 143 description = db.Column(db.Text) 144 instructions = db.Column(db.Text) 145 deleted = db.Column(db.Boolean, default=False) 146 playground = db.Column(db.Boolean, default=False) 147 148 # relations 149 owner_id = db.Column(db.Integer, db.ForeignKey("user.id")) 150 owner = db.relationship("User", backref=db.backref("coprs")) 151 mock_chroots = association_proxy("copr_chroots", "mock_chroot") 152 153 __mapper_args__ = { 154 "order_by": created_on.desc() 155 } 156 157 @property
158 - def repos_list(self):
159 """ 160 Return repos of this copr as a list of strings 161 """ 162 return self.repos.split()
163 164 @property
165 - def active_chroots(self):
166 """ 167 Return list of active mock_chroots of this copr 168 """ 169 170 return filter(lambda x: x.is_active, self.mock_chroots)
171 172 @property
173 - def build_count(self):
174 """ 175 Return number of builds in this copr 176 """ 177 178 return len(self.builds)
179
180 - def check_copr_chroot(self, chroot):
181 """ 182 Return object of chroot, if is related to our copr or None 183 """ 184 185 result = None 186 # there will be max ~10 chroots per build, iteration will be probably 187 # faster than sql query 188 for copr_chroot in self.copr_chroots: 189 if copr_chroot.mock_chroot_id == chroot.id: 190 result = copr_chroot 191 break 192 return result
193
194 - def buildroot_pkgs(self, chroot):
195 """ 196 Return packages in minimal buildroot for given chroot. 197 """ 198 199 result = "" 200 # this is ugly as user can remove chroot after he submit build, but 201 # lets call this feature 202 copr_chroot = self.check_copr_chroot(chroot) 203 if copr_chroot: 204 result = copr_chroot.buildroot_pkgs 205 return result
206 207 @property
208 - def modified_chroots(self):
209 """ 210 Return list of chroots which has been modified 211 """ 212 modified_chroots=[] 213 for chroot in self.active_chroots: 214 if self.buildroot_pkgs(chroot): 215 modified_chroots.append(chroot) 216 return modified_chroots
217
218 219 -class CoprPermission(db.Model, helpers.Serializer):
220 221 """ 222 Association class for Copr<->Permission relation 223 """ 224 225 # see helpers.PermissionEnum for possible values of the fields below 226 # can this user build in the copr? 227 copr_builder = db.Column(db.SmallInteger, default=0) 228 # can this user serve as an admin? (-> edit and approve permissions) 229 copr_admin = db.Column(db.SmallInteger, default=0) 230 231 # relations 232 user_id = db.Column(db.Integer, db.ForeignKey("user.id"), primary_key=True) 233 user = db.relationship("User", backref=db.backref("copr_permissions")) 234 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True) 235 copr = db.relationship("Copr", backref=db.backref("copr_permissions"))
236
237 238 -class Build(db.Model, helpers.Serializer):
239 240 """ 241 Representation of one build in one copr 242 """ 243 244 id = db.Column(db.Integer, primary_key=True) 245 # list of space separated urls of packages to build 246 pkgs = db.Column(db.Text) 247 # built packages 248 built_packages = db.Column(db.Text) 249 # version of the srpm package got by rpm 250 pkg_version = db.Column(db.Text) 251 # was this build canceled by user? 252 canceled = db.Column(db.Boolean, default=False) 253 # list of space separated additional repos 254 repos = db.Column(db.Text) 255 # the three below represent time of important events for this build 256 # as returned by int(time.time()) 257 submitted_on = db.Column(db.Integer, nullable=False) 258 started_on = db.Column(db.Integer) 259 ended_on = db.Column(db.Integer) 260 # url of the build results 261 results = db.Column(db.Text) 262 # memory requirements for backend builder 263 memory_reqs = db.Column(db.Integer, default=constants.DEFAULT_BUILD_MEMORY) 264 # maximum allowed time of build, build will fail if exceeded 265 timeout = db.Column(db.Integer, default=constants.DEFAULT_BUILD_TIMEOUT) 266 267 # relations 268 user_id = db.Column(db.Integer, db.ForeignKey("user.id")) 269 user = db.relationship("User", backref=db.backref("builds")) 270 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id")) 271 copr = db.relationship("Copr", backref=db.backref("builds")) 272 273 chroots = association_proxy("build_chroots", "mock_chroot") 274 275 @property
276 - def chroot_states(self):
277 return map(lambda chroot: chroot.status, self.build_chroots)
278 279 @property
280 - def has_pending_chroot(self):
281 # FIXME bad name 282 # used when checking if the repo is initialized and results can be set 283 # i think this is the only purpose - check 284 return helpers.StatusEnum("pending") in self.chroot_states or \ 285 helpers.StatusEnum("starting") in self.chroot_states
286 287 @property
288 - def has_unfinished_chroot(self):
289 return helpers.StatusEnum("pending") in self.chroot_states or \ 290 helpers.StatusEnum("starting") in self.chroot_states or \ 291 helpers.StatusEnum("running") in self.chroot_states
292 293 @property
294 - def status(self):
295 """ 296 Return build status according to build status of its chroots 297 """ 298 299 if self.canceled: 300 return helpers.StatusEnum("canceled") 301 302 for state in ["failed", "running", "starting", "pending", "succeeded", "skipped"]: 303 if helpers.StatusEnum(state) in self.chroot_states: 304 return helpers.StatusEnum(state)
305 306 @property
307 - def state(self):
308 """ 309 Return text representation of status of this build 310 """ 311 312 if self.status is not None: 313 return helpers.StatusEnum(self.status) 314 315 return "unknown"
316 317 @property
318 - def cancelable(self):
319 """ 320 Find out if this build is cancelable. 321 322 Build is cancelabel only when it's pending (not started) 323 """ 324 325 return self.status == helpers.StatusEnum("pending")
326 327 @property
328 - def repeatable(self):
329 """ 330 Find out if this build is repeatable. 331 332 Build is repeatable only if it's not pending, starting or running 333 """ 334 335 return self.status != helpers.StatusEnum("pending") and \ 336 self.status != helpers.StatusEnum("starting") and \ 337 self.status != helpers.StatusEnum("running")
338 339 @property
340 - def deletable(self):
341 """ 342 Find out if this build is deletable. 343 344 Build is deletable only when it's finished. (also means cancelled) 345 It is important to remember that "failed" state doesn't ultimately 346 mean it's finished - so we need to check whether the "ended_on" 347 property has been set. 348 """ 349 350 return self.state in ["succeeded", "canceled", "skipped"] or \ 351 (self.state == "failed" and self.ended_on is not None)
352
353 354 -class MockChroot(db.Model, helpers.Serializer):
355 356 """ 357 Representation of mock chroot 358 """ 359 360 id = db.Column(db.Integer, primary_key=True) 361 # fedora/epel/..., mandatory 362 os_release = db.Column(db.String(50), nullable=False) 363 # 18/rawhide/..., optional (mock chroot doesn"t need to have this) 364 os_version = db.Column(db.String(50), nullable=False) 365 # x86_64/i686/..., mandatory 366 arch = db.Column(db.String(50), nullable=False) 367 is_active = db.Column(db.Boolean, default=True) 368 369 @property
370 - def name(self):
371 """ 372 Textual representation of name of this chroot 373 """ 374 return "{0}-{1}-{2}".format( 375 self.os_release, self.os_version, self.arch)
376 377 @property
378 - def os(self):
379 """ 380 Textual representation of the operating system name 381 """ 382 return "{0} {1}".format(self.os_release, self.os_version)
383
384 385 -class CoprChroot(db.Model, helpers.Serializer):
386 387 """ 388 Representation of Copr<->MockChroot relation 389 """ 390 391 buildroot_pkgs = db.Column(db.Text) 392 mock_chroot_id = db.Column( 393 db.Integer, db.ForeignKey("mock_chroot.id"), primary_key=True) 394 mock_chroot = db.relationship( 395 "MockChroot", backref=db.backref("copr_chroots")) 396 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), primary_key=True) 397 copr = db.relationship("Copr", 398 backref=db.backref( 399 "copr_chroots", 400 single_parent=True, 401 cascade="all,delete,delete-orphan"))
402
403 404 -class BuildChroot(db.Model, helpers.Serializer):
405 406 """ 407 Representation of Build<->MockChroot relation 408 """ 409 410 mock_chroot_id = db.Column(db.Integer, db.ForeignKey("mock_chroot.id"), 411 primary_key=True) 412 mock_chroot = db.relationship("MockChroot", backref=db.backref("builds")) 413 build_id = db.Column(db.Integer, db.ForeignKey("build.id"), 414 primary_key=True) 415 build = db.relationship("Build", backref=db.backref("build_chroots")) 416 status = db.Column(db.Integer, default=helpers.StatusEnum("pending")) 417 418 @property
419 - def name(self):
420 """ 421 Textual representation of name of this chroot 422 """ 423 424 return self.mock_chroot.name
425 426 @property
427 - def state(self):
428 """ 429 Return text representation of status of this build chroot 430 """ 431 432 if self.status is not None: 433 return helpers.StatusEnum(self.status) 434 435 return "unknown"
436
437 438 -class LegalFlag(db.Model, helpers.Serializer):
439 id = db.Column(db.Integer, primary_key=True) 440 # message from user who raised the flag (what he thinks is wrong) 441 raise_message = db.Column(db.Text) 442 # time of raising the flag as returned by int(time.time()) 443 raised_on = db.Column(db.Integer) 444 # time of resolving the flag by admin as returned by int(time.time()) 445 resolved_on = db.Column(db.Integer) 446 447 # relations 448 copr_id = db.Column(db.Integer, db.ForeignKey("copr.id"), nullable=True) 449 # cascade="all" means that we want to keep these even if copr is deleted 450 copr = db.relationship( 451 "Copr", backref=db.backref("legal_flags", cascade="all")) 452 # user who reported the problem 453 reporter_id = db.Column(db.Integer, db.ForeignKey("user.id")) 454 reporter = db.relationship("User", 455 backref=db.backref("legal_flags_raised"), 456 foreign_keys=[reporter_id], 457 primaryjoin="LegalFlag.reporter_id==User.id") 458 # admin who resolved the problem 459 resolver_id = db.Column( 460 db.Integer, db.ForeignKey("user.id"), nullable=True) 461 resolver = db.relationship("User", 462 backref=db.backref("legal_flags_resolved"), 463 foreign_keys=[resolver_id], 464 primaryjoin="LegalFlag.resolver_id==User.id")
465
466 467 -class Action(db.Model, helpers.Serializer):
468 469 """ 470 Representation of a custom action that needs 471 backends cooperation/admin attention/... 472 """ 473 474 id = db.Column(db.Integer, primary_key=True) 475 # delete, rename, ...; see helpers.ActionTypeEnum 476 action_type = db.Column(db.Integer, nullable=False) 477 # copr, ...; downcase name of class of modified object 478 object_type = db.Column(db.String(20)) 479 # id of the modified object 480 object_id = db.Column(db.Integer) 481 # old and new values of the changed property 482 old_value = db.Column(db.String(255)) 483 new_value = db.Column(db.String(255)) 484 # additional data 485 data = db.Column(db.Text) 486 # result of the action, see helpers.BackendResultEnum 487 result = db.Column( 488 db.Integer, default=helpers.BackendResultEnum("waiting")) 489 # optional message from the backend/whatever 490 message = db.Column(db.Text) 491 # time created as returned by int(time.time()) 492 created_on = db.Column(db.Integer) 493 # time ended as returned by int(time.time()) 494 ended_on = db.Column(db.Integer) 495
496 - def __str__(self):
497 return self.__unicode__()
498
499 - def __unicode__(self):
500 if self.action_type == helpers.ActionTypeEnum("delete"): 501 return "Deleting {0} {1}".format(self.object_type, self.old_value) 502 elif self.action_type == helpers.ActionTypeEnum("rename"): 503 return "Renaming {0} from {1} to {2}.".format(self.object_type, 504 self.old_value, 505 self.new_value) 506 elif self.action_type == helpers.ActionTypeEnum("legal-flag"): 507 return "Legal flag on copr {0}.".format(self.old_value) 508 509 return "Action {0} on {1}, old value: {2}, new value: {3}.".format( 510 self.action_type, self.object_type, self.old_value, self.new_value)
511