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

Source Code for Module coprs.models

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