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

Source Code for Module coprs.models

  1  import datetime 
  2  import markdown 
  3   
  4  from sqlalchemy.ext.associationproxy import association_proxy 
  5  from libravatar import libravatar_url 
  6   
  7  from coprs import constants 
  8  from coprs import db 
  9  from coprs import helpers 
10 11 -class Serializer(object):
12 - def to_dict(self, options = {}):
13 """Usage: 14 SQLAlchObject.to_dict() => returns a flat dict of the object 15 SQLAlchObject.to_dict({'foo': {}}) => returns a dict of the object and will include a 16 flat dict of object foo inside of that 17 SQLAlchObject.to_dict({'foo': {'bar': {}}, 'spam': {}}) => returns a dict of the object, 18 which will include dict of foo (which will include dict of bar) and dict of spam 19 20 Options can also contain two special values: __columns_only__ and __columns_except__ 21 If present, the first makes only specified fiels appear, the second removes specified fields. 22 Both of these fields must be either strings (only works for one field) or lists (for one and more fields). 23 SQLAlchObject.to_dict({'foo': {'__columns_except__': ['id']}, '__columns_only__': 'name'}) => 24 The SQLAlchObject will only put its 'name' into the resulting dict, while 'foo' all of its fields except 'id'. 25 26 Options can also specify whether to include foo_id when displaying related foo object 27 (__included_ids__, defaults to True). This doesn't apply when __columns_only__ is specified. 28 """ 29 result = {} 30 columns = self.serializable_attributes 31 32 if options.has_key('__columns_only__'): 33 columns = options['__columns_only__'] 34 else: 35 columns = set(columns) 36 if options.has_key('__columns_except__'): 37 columns_except = options['__columns_except__'] if isinstance(options['__columns_except__'], list) else [options['__columns_except__']] 38 columns -= set(columns_except) 39 if options.has_key('__included_ids__') and options['__included_ids__'] == False: 40 related_objs_ids = [r + '_id' for r, o in options.items() if not r.startswith('__')] 41 columns -= set(related_objs_ids) 42 43 columns = list(columns) 44 45 for column in columns: 46 result[column] = getattr(self, column) 47 48 for related, values in options.items(): 49 if hasattr(self, related): 50 result[related] = getattr(self, related).to_dict(values) 51 return result
52 53 @property
54 - def serializable_attributes(self):
55 return map(lambda x: x.name, self.__table__.columns)
56
57 -class User(db.Model, Serializer):
58 """Represents user of the copr frontend""" 59 id = db.Column(db.Integer, primary_key = True) 60 # openid_name for fas, e.g. http://bkabrda.id.fedoraproject.org/ 61 openid_name = db.Column(db.String(100), nullable = False) 62 # just mail :) 63 mail = db.Column(db.String(150), nullable = False) 64 # just timezone ;) 65 timezone = db.Column(db.String(50), nullable = True) 66 # is this user proven? proven users can modify builder memory and timeout for single builds 67 proven = db.Column(db.Boolean, default = False) 68 # is this user admin of the system? 69 admin = db.Column(db.Boolean, default = False) 70 # stuff for the cli interface 71 api_login = db.Column(db.String(40), nullable = False, default = 'abc') 72 api_token = db.Column(db.String(40), nullable = False, default = 'abc') 73 api_token_expiration = db.Column(db.Date, nullable = False, default = datetime.date(2000, 1, 1)) 74 75 @property
76 - def name(self):
77 """Returns the short username of the user, e.g. bkabrda""" 78 return self.openid_name.replace('.id.fedoraproject.org/', '').replace('http://', '')
79
80 - def permissions_for_copr(self, copr):
81 """Get permissions of this user for the given copr. 82 Caches the permission during one request, so use this if you access them multiple times 83 """ 84 if not hasattr(self, '_permissions_for_copr'): 85 self._permissions_for_copr = {} 86 if not copr.name in self._permissions_for_copr: 87 self._permissions_for_copr[copr.name] = CoprPermission.query.filter_by(user = self).filter_by(copr = copr).first() 88 return self._permissions_for_copr[copr.name]
89
90 - def can_build_in(self, copr):
91 """Determine if this user can build in the given copr.""" 92 can_build = False 93 if copr.owner == self: 94 can_build = True 95 if self.permissions_for_copr(copr) and self.permissions_for_copr(copr).copr_builder == helpers.PermissionEnum('approved'): 96 can_build = True 97 98 return can_build
99
100 - def can_edit(self, copr):
101 """Determine if this user can edit the given copr.""" 102 can_edit = False 103 if copr.owner == self: 104 can_edit = True 105 if self.permissions_for_copr(copr) and self.permissions_for_copr(copr).copr_admin == helpers.PermissionEnum('approved'): 106 can_edit = True 107 108 return can_edit
109 110 @classmethod
111 - def openidize_name(cls, name):
112 """Creates proper openid_name from short name. 113 114 >>> user.openid_name == User.openidize_name(user.name) 115 True 116 """ 117 return 'http://{0}.id.fedoraproject.org/'.format(name)
118 119 @property
120 - def serializable_attributes(self):
121 # enumerate here to prevent exposing credentials 122 return ['id', 'name']
123 124 @property
125 - def coprs_count(self):
126 """Get number of coprs for this user.""" 127 return Copr.query.filter_by(owner=self).\ 128 filter_by(deleted=False).\ 129 count()
130 131 @property
132 - def gravatar_url(self):
133 """Return url to libravatar image.""" 134 try: 135 return libravatar_url(email = self.mail) 136 except IOError: 137 return ""
138
139 -class Copr(db.Model, Serializer):
140 """Represents a single copr (private repo with builds, mock chroots, etc.).""" 141 id = db.Column(db.Integer, primary_key = True) 142 # name of the copr, no fancy chars (checked by forms) 143 name = db.Column(db.String(100), nullable = False) 144 # string containing urls of additional repos (separated by space) 145 # that this copr will pull dependencies from 146 repos = db.Column(db.Text) 147 # time of creation as returned by int(time.time()) 148 created_on = db.Column(db.Integer) 149 # description and instructions given by copr owner 150 description = db.Column(db.Text) 151 instructions = db.Column(db.Text) 152 deleted = db.Column(db.Boolean, default=False) 153 154 # relations 155 owner_id = db.Column(db.Integer, db.ForeignKey('user.id')) 156 owner = db.relationship('User', backref = db.backref('coprs')) 157 mock_chroots = association_proxy('copr_chroots', 'mock_chroot') 158 159 __mapper_args__ = { 160 'order_by' : created_on.desc() 161 } 162 163 @property
164 - def repos_list(self):
165 """Returns repos of this copr as a list of strings""" 166 return self.repos.split()
167 168 @property
170 md = markdown.Markdown(safe_mode='replace', 171 html_replacement_text='--RAW HTML NOT ALLOWED--') 172 return md.convert(self.description) or 'Description not filled in by author. Very likely personal repository for testing purpose, which you should not use.'
173 174 @property
176 md = markdown.Markdown(safe_mode='replace', 177 html_replacement_text='--RAW HTML NOT ALLOWED--') 178 return md.convert(self.instructions) or 'Instructions not filled in by author. Author knows what to do. Everybody else should avoid this repo.'
179 180 @property
181 - def active_chroots(self):
182 """Returns list of active mock_chroots of this copr""" 183 return filter(lambda x: x.is_active, self.mock_chroots)
184 185 @property
186 - def build_count(self):
187 """ Return number of builds in this copr """ 188 189 return len(self.builds)
190
191 - def check_copr_chroot(self, chroot):
192 """Return object of chroot, if is related to our copr or None""" 193 result = None 194 # there will be max ~10 chroots per build, iteration will be probably faster than sql query 195 for copr_chroot in self.copr_chroots: 196 if copr_chroot.mock_chroot_id == chroot.id: 197 result = copr_chroot 198 break 199 return result
200
201 - def buildroot_pkgs(self, chroot):
202 """Returns packages in minimal buildroot for given chroot.""" 203 result = '' 204 # this is ugly as user can remove chroot after he submit build, but lets call this feature 205 copr_chroot = self.check_copr_chroot(chroot) 206 if copr_chroot: 207 result = copr_chroot.buildroot_pkgs 208 return result
209
210 -class CoprPermission(db.Model, Serializer):
211 """Association class for Copr<->Permission relation""" 212 ## see helpers.PermissionEnum for possible values of the fields below 213 # can this user build in the copr? 214 copr_builder = db.Column(db.SmallInteger, default = 0) 215 # can this user serve as an admin? (-> edit and approve permissions) 216 copr_admin = db.Column(db.SmallInteger, default = 0) 217 218 # relations 219 user_id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key = True) 220 user = db.relationship('User', backref = db.backref('copr_permissions')) 221 copr_id = db.Column(db.Integer, db.ForeignKey('copr.id'), primary_key = True) 222 copr = db.relationship('Copr', backref = db.backref('copr_permissions'))
223
224 -class Build(db.Model, Serializer):
225 """Representation of one build in one copr""" 226 id = db.Column(db.Integer, primary_key = True) 227 # list of space separated urls of packages to build 228 pkgs = db.Column(db.Text) 229 # was this build canceled by user? 230 canceled = db.Column(db.Boolean, default = False) 231 # list of space separated additional repos 232 repos = db.Column(db.Text) 233 ## the three below represent time of important events for this build 234 ## as returned by int(time.time()) 235 submitted_on = db.Column(db.Integer, nullable = False) 236 started_on = db.Column(db.Integer) 237 ended_on = db.Column(db.Integer) 238 # url of the build results 239 results = db.Column(db.Text) 240 # memory requirements for backend builder 241 memory_reqs = db.Column(db.Integer, default = constants.DEFAULT_BUILD_MEMORY) 242 # maximum allowed time of build, build will fail if exceeded 243 timeout = db.Column(db.Integer, default = constants.DEFAULT_BUILD_TIMEOUT) 244 245 # relations 246 user_id = db.Column(db.Integer, db.ForeignKey('user.id')) 247 user = db.relationship('User', backref = db.backref('builds')) 248 copr_id = db.Column(db.Integer, db.ForeignKey('copr.id')) 249 copr = db.relationship('Copr', backref = db.backref('builds')) 250 251 chroots = association_proxy('build_chroots', 'mock_chroot') 252 253 @property
254 - def chroot_states(self):
255 return map(lambda chroot: chroot.status, self.build_chroots)
256 257 @property
258 - def has_pending_chroot(self):
259 return helpers.StatusEnum('pending') in self.chroot_states
260 261 @property
262 - def status(self):
263 """ Return build status according to build status of its chroots """ 264 if self.canceled: 265 return helpers.StatusEnum('canceled') 266 267 for state in ['failed', 'running', 'pending', 'succeeded']: 268 if helpers.StatusEnum(state) in self.chroot_states: 269 return helpers.StatusEnum(state)
270 271 @property
272 - def state(self):
273 """ Return text representation of status of this build """ 274 if self.status is not None: 275 return helpers.StatusEnum(self.status) 276 277 return 'unknown'
278 279 @property
280 - def cancelable(self):
281 """ 282 Find out if this build is cancelable. 283 284 ATM, build is cancelable only if it wasn't grabbed by backend. 285 """ 286 287 return self.status == helpers.StatusEnum('pending')
288
289 290 -class MockChroot(db.Model, Serializer):
291 """Representation of mock chroot""" 292 id = db.Column(db.Integer, primary_key = True) 293 # fedora/epel/..., mandatory 294 os_release = db.Column(db.String(50), nullable = False) 295 # 18/rawhide/..., optional (mock chroot doesn't need to have this) 296 os_version = db.Column(db.String(50), nullable = False) 297 # x86_64/i686/..., mandatory 298 arch = db.Column(db.String(50), nullable = False) 299 is_active = db.Column(db.Boolean, default = True) 300 301 @property
302 - def name(self):
303 """Textual representation of name of this chroot""" 304 if self.os_version: 305 format_string = '{rel}-{ver}-{arch}' 306 else: 307 format_string = '{rel}-{arch}' 308 return format_string.format(rel=self.os_release, 309 ver=self.os_version, 310 arch=self.arch)
311
312 -class CoprChroot(db.Model, Serializer):
313 """Representation of Copr<->MockChroot relation""" 314 buildroot_pkgs = db.Column(db.Text) 315 mock_chroot_id = db.Column(db.Integer, db.ForeignKey('mock_chroot.id'), primary_key = True) 316 mock_chroot = db.relationship('MockChroot', backref = db.backref('copr_chroots')) 317 copr_id = db.Column(db.Integer, db.ForeignKey('copr.id'), primary_key = True) 318 copr = db.relationship('Copr', backref = db.backref('copr_chroots', 319 single_parent=True, 320 cascade='all,delete,delete-orphan'))
321
322 323 -class BuildChroot(db.Model, Serializer):
324 """Representation of Build<->MockChroot relation""" 325 mock_chroot_id = db.Column(db.Integer, db.ForeignKey('mock_chroot.id'), 326 primary_key=True) 327 mock_chroot = db.relationship('MockChroot', backref=db.backref('builds')) 328 build_id = db.Column(db.Integer, db.ForeignKey('build.id'), 329 primary_key=True) 330 build = db.relationship('Build', backref=db.backref('build_chroots')) 331 status = db.Column(db.Integer, default=helpers.StatusEnum('pending')) 332 333 @property
334 - def name(self):
335 """ Textual representation of name of this chroot """ 336 return self.mock_chroot.name
337 338 @property
339 - def state(self):
340 """ Return text representation of status of this build chroot """ 341 if self.status is not None: 342 return helpers.StatusEnum(self.status) 343 344 return 'unknown'
345
346 347 348 -class LegalFlag(db.Model, Serializer):
349 id = db.Column(db.Integer, primary_key=True) 350 # message from user who raised the flag (what he thinks is wrong) 351 raise_message = db.Column(db.Text) 352 # time of raising the flag as returned by int(time.time()) 353 raised_on = db.Column(db.Integer) 354 # time of resolving the flag by admin as returned by int(time.time()) 355 resolved_on = db.Column(db.Integer) 356 357 # relations 358 copr_id = db.Column(db.Integer, db.ForeignKey('copr.id'), nullable=True) 359 # cascade='all' means that we want to keep these even if copr is deleted 360 copr = db.relationship('Copr', backref=db.backref('legal_flags', cascade='all')) 361 # user who reported the problem 362 reporter_id = db.Column(db.Integer, db.ForeignKey('user.id')) 363 reporter = db.relationship('User', 364 backref=db.backref('legal_flags_raised'), 365 foreign_keys=[reporter_id], 366 primaryjoin='LegalFlag.reporter_id==User.id') 367 # admin who resolved the problem 368 resolver_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True) 369 resolver = db.relationship('User', 370 backref=db.backref('legal_flags_resolved'), 371 foreign_keys=[resolver_id], 372 primaryjoin='LegalFlag.resolver_id==User.id')
373
374 375 -class Action(db.Model, Serializer):
376 """Representation of a custom action that needs backends cooperation/admin attention/...""" 377 id = db.Column(db.Integer, primary_key=True) 378 # delete, rename, ...; see helpers.ActionTypeEnum 379 action_type = db.Column(db.Integer, nullable=False) 380 # copr, ...; downcase name of class of modified object 381 object_type = db.Column(db.String(20)) 382 # id of the modified object 383 object_id = db.Column(db.Integer) 384 # old and new values of the changed property 385 old_value = db.Column(db.String(255)) 386 new_value = db.Column(db.String(255)) 387 # additional data 388 data = db.Column(db.Text) 389 # result of the action, see helpers.BackendResultEnum 390 result = db.Column(db.Integer, default=helpers.BackendResultEnum('waiting')) 391 # optional message from the backend/whatever 392 message = db.Column(db.Text) 393 # time created as returned by int(time.time()) 394 created_on = db.Column(db.Integer) 395 # time ended as returned by int(time.time()) 396 ended_on = db.Column(db.Integer) 397
398 - def __str__(self):
399 return self.__unicode__()
400
401 - def __unicode__(self):
402 if self.action_type == helpers.ActionTypeEnum('delete'): 403 return 'Deleting {0} {1}'.format(self.object_type, self.old_value) 404 elif self.action_type == helpers.ActionTypeEnum('rename'): 405 return 'Renaming {0} from {1} to {2}.'.format(self.object_type, 406 self.old_value, 407 self.new_value) 408 elif self.action_type == helpers.ActionTypeEnum('legal-flag'): 409 return 'Legal flag on copr {0}.'.format(self.old_value) 410 411 return 'Action {0} on {1}, old value: {2}, new value: {3}.'.format(self.action_type, 412 self.object_type, 413 self.old_value, 414 self.new_value)
415