class OAuth2Client(db.Datetimed, db.Document): secret = db.StringField(default=lambda: gen_salt(50), required=True) type = db.StringField(choices=CLIENT_TYPES.keys(), default='public', required=True) profile = db.StringField(choices=CLIENT_PROFILES.keys(), default='web', required=True) grant_type = db.StringField(choices=GRANT_TYPES.keys(), default='code', required=True) name = db.StringField(required=True) description = db.StringField() owner = db.ReferenceField('User') organization = db.ReferenceField('Organization') image = db.ImageField(fs=images, basename=default_image_basename, thumbnails=[150, 25]) redirect_uris = db.ListField(db.StringField()) default_scopes = db.ListField(db.StringField(), default=SCOPES.keys()) meta = { 'collection': 'oauth2_client' } @property def client_id(self): return str(self.id) @property def client_secret(self): return self.secret @property def default_redirect_uri(self): return self.redirect_uris[0]
class Post(db.Datetimed, db.Document): name = db.StringField(max_length=255, required=True) slug = db.SlugField(max_length=255, required=True, populate_from='name', update=True) headline = db.StringField() content = db.StringField(required=True) image_url = db.StringField() image = db.ImageField(fs=images, basename=default_image_basename, thumbnails=IMAGE_SIZES) credit_to = db.StringField() credit_url = db.URLField() tags = db.ListField(db.StringField()) datasets = db.ListField( db.ReferenceField('Dataset', reverse_delete_rule=db.PULL)) reuses = db.ListField( db.ReferenceField('Reuse', reverse_delete_rule=db.PULL)) owner = db.ReferenceField('User') private = db.BooleanField() meta = { 'ordering': ['-created_at'], } def __unicode__(self): return self.name or '' @property def display_url(self): return url_for('posts.show', post=self)
class Post(db.Datetimed, db.Document): name = db.StringField(max_length=255, required=True) slug = db.SlugField(max_length=255, required=True, populate_from='name', update=True, follow=True) headline = db.StringField() content = db.StringField(required=True) image_url = db.StringField() image = db.ImageField(fs=images, basename=default_image_basename, thumbnails=IMAGE_SIZES) credit_to = db.StringField() credit_url = db.URLField() tags = db.ListField(db.StringField()) datasets = db.ListField( db.ReferenceField('Dataset', reverse_delete_rule=db.PULL)) reuses = db.ListField( db.ReferenceField('Reuse', reverse_delete_rule=db.PULL)) owner = db.ReferenceField('User') published = db.DateTimeField() meta = { 'ordering': ['-created_at'], 'indexes': [ '-created_at', '-published', ], 'queryset_class': PostQuerySet, } verbose_name = _('post') def __str__(self): return self.name or '' def url_for(self, *args, **kwargs): return url_for('posts.show', post=self, *args, **kwargs) @property def display_url(self): return self.url_for() @property def external_url(self): return self.url_for(_external=True) def count_discussions(self): # There are no metrics on Post to store discussions count pass
class Reuse(db.Datetimed, WithMetrics, BadgeMixin, db.Owned, db.Document): title = db.StringField(required=True) slug = db.SlugField(max_length=255, required=True, populate_from='title', update=True, follow=True) description = db.StringField(required=True) type = db.StringField(required=True, choices=list(REUSE_TYPES)) url = db.StringField(required=True) urlhash = db.StringField(required=True, unique=True) image_url = db.StringField() image = db.ImageField(fs=images, basename=default_image_basename, max_size=IMAGE_MAX_SIZE, thumbnails=IMAGE_SIZES) datasets = db.ListField( db.ReferenceField('Dataset', reverse_delete_rule=db.PULL)) tags = db.TagListField() topic = db.StringField(required=True, choices=list(REUSE_TOPICS)) # badges = db.ListField(db.EmbeddedDocumentField(ReuseBadge)) private = db.BooleanField() ext = db.MapField(db.GenericEmbeddedDocumentField()) extras = db.ExtrasField() featured = db.BooleanField() deleted = db.DateTimeField() def __str__(self): return self.title or '' __badges__ = {} __metrics_keys__ = [ 'discussions', 'datasets', 'followers', 'views', ] meta = { 'indexes': [ '$title', 'created_at', 'last_modified', 'metrics.datasets', 'metrics.followers', 'metrics.views', 'urlhash' ] + db.Owned.meta['indexes'], 'ordering': ['-created_at'], 'queryset_class': ReuseQuerySet, } before_save = Signal() after_save = Signal() on_create = Signal() on_update = Signal() before_delete = Signal() after_delete = Signal() on_delete = Signal() verbose_name = _('reuse') @classmethod def pre_save(cls, sender, document, **kwargs): # Emit before_save cls.before_save.send(document) @classmethod def post_save(cls, sender, document, **kwargs): if 'post_save' in kwargs.get('ignores', []): return cls.after_save.send(document) if kwargs.get('created'): cls.on_create.send(document) else: cls.on_update.send(document) if document.deleted: cls.on_delete.send(document) def url_for(self, *args, **kwargs): return endpoint_for('reuses.show', 'api.reuse', reuse=self, *args, **kwargs) display_url = property(url_for) @property def is_visible(self): return not self.is_hidden @property def is_hidden(self): return len(self.datasets) == 0 or self.private or self.deleted @property def external_url(self): return self.url_for(_external=True) @property def type_label(self): return REUSE_TYPES[self.type] @property def topic_label(self): return REUSE_TOPICS[self.topic] def clean(self): super(Reuse, self).clean() '''Auto populate urlhash from url''' if not self.urlhash or 'url' in self._get_changed_fields(): self.urlhash = hash_url(self.url) @classmethod def get(cls, id_or_slug): obj = cls.objects(slug=id_or_slug).first() return obj or cls.objects.get_or_404(id=id_or_slug) @classmethod def url_exists(cls, url): urlhash = hash_url(url) return cls.objects(urlhash=urlhash).count() > 0 @cached_property def json_ld(self): result = { '@context': 'http://schema.org', '@type': 'CreativeWork', 'alternateName': self.slug, 'dateCreated': self.created_at.isoformat(), 'dateModified': self.last_modified.isoformat(), 'url': endpoint_for('reuses.show', 'api.reuse', reuse=self, _external=True), 'name': self.title, 'isBasedOnUrl': self.url, } if self.description: result['description'] = mdstrip(self.description) if self.organization: author = self.organization.json_ld elif self.owner: author = self.owner.json_ld else: author = None if author: result['author'] = author return result @property def views_count(self): return self.metrics.get('views', 0) def count_datasets(self): self.metrics['datasets'] = len(self.datasets) self.save(signal_kwargs={'ignores': ['post_save']}) def count_discussions(self): from udata.models import Discussion self.metrics['discussions'] = Discussion.objects(subject=self, closed=None).count() self.save() def count_followers(self): from udata.models import Follow self.metrics['followers'] = Follow.objects( until=None).followers(self).count() self.save()
class GeoZone(db.Document): id = db.StringField(primary_key=True) slug = db.StringField(required=True) name = db.StringField(required=True) level = db.StringField(required=True) code = db.StringField(required=True) geom = db.MultiPolygonField() parents = db.ListField() keys = db.DictField() validity = db.EmbeddedDocumentField(db.DateRange) ancestors = db.ListField() successors = db.ListField() population = db.IntField() area = db.FloatField() wikipedia = db.StringField() dbpedia = db.StringField() flag = db.ImageField(fs=logos) blazon = db.ImageField(fs=logos) logo = db.ImageField(fs=logos) meta = { 'indexes': [ 'name', 'parents', ('level', 'code'), ], 'queryset_class': GeoZoneQuerySet } def __unicode__(self): return self.id __str__ = __unicode__ def __html__(self): """In use within the admin.""" return '{name} <i>({code})</i>'.format(name=gettext(self.name), code=self.code) def logo_url(self, external=False): flag_filename = self.flag.filename blazon_filename = self.blazon.filename if flag_filename and self.flag.fs.exists(flag_filename): return self.flag.fs.url(flag_filename, external=external) elif blazon_filename and self.blazon.fs.exists(blazon_filename): return self.blazon.fs.url(blazon_filename, external=external) else: return '' @property def keys_values(self): """Key values might be a list or not, always return a list.""" keys_values = [] for value in self.keys.values(): if isinstance(value, list): keys_values += value elif not str(value).startswith('-'): # Avoid -99. keys_values.append(value) return keys_values @cached_property def level_code(self): """Truncated level code for the sake of readability.""" # Either 'region', 'departement' or 'commune', # useful to match TERRITORY_DATASETS keys. return self.id.split(':')[1] @cached_property def level_name(self): """Truncated level name for the sake of readability.""" if self.level.startswith('fr:'): return self.level[3:] # Keep the whole level name as a fallback (e.g. `country:fr`) return self.level @cached_property def level_i18n_name(self): """In use within templates for dynamic translations.""" for level, name in spatial_granularities: if self.level == level: return name return self.level_name # Fallback that should never happen. @cached_property def ancestors_objects(self): """Ancestors objects sorted by name.""" ancestors_objects = [] for ancestor in self.ancestors: try: ancestor_object = GeoZone.objects.get(id=ancestor) except GeoZone.DoesNotExist: continue ancestors_objects.append(ancestor_object) ancestors_objects.sort(key=lambda a: a.name) return ancestors_objects @cached_property def child_level(self): """Return the child level given handled levels.""" HANDLED_LEVELS = current_app.config.get('HANDLED_LEVELS') try: return HANDLED_LEVELS[HANDLED_LEVELS.index(self.level) - 1] except (IndexError, ValueError): return None @cached_property def parent_level(self): """Return the parent level given handled levels.""" HANDLED_LEVELS = current_app.config.get('HANDLED_LEVELS') try: return HANDLED_LEVELS[HANDLED_LEVELS.index(self.level) + 1] except (IndexError, ValueError): return None @property def url(self): return url_for('territories.territory', territory=self) @property def external_url(self): return url_for('territories.territory', territory=self, _external=True) @cached_property def wikipedia_url(self): """Computed wikipedia URL from the DBpedia one.""" return (self.dbpedia.replace('dbpedia', 'wikipedia').replace('resource', 'wiki')) @cached_property def postal_string(self): """Return a list of postal codes separated by commas.""" return ', '.join(self.keys.get('postal', [])) @property def parents_objects(self): if self.parent_level: for parent in self.parents: if parent.startswith(self.parent_level): yield GeoZone.objects.get(id=parent, level=self.parent_level) @cached_property def current_parent(self): today = date.today() for parent in self.parents_objects: if parent.valid_at(today): return parent @property def children(self): return (GeoZone.objects(level=self.child_level, parents__in=[self.id]).order_by('name')) @property def biggest_children(self): return self.children.order_by('-population', '-area')[:10] @property def handled_level(self): return self.level in current_app.config.get('HANDLED_LEVELS') def valid_at(self, valid_date): if not self.validity: return True return self.validity.start <= valid_date <= self.validity.end def toGeoJSON(self): return { 'id': self.id, 'type': 'Feature', 'geometry': self.geom, 'properties': { 'slug': self.slug, 'name': gettext(self.name), 'level': self.level, 'code': self.code, 'validity': self.validity, 'parents': self.parents, 'keys': self.keys, 'population': self.population, 'area': self.area, 'logo': self.logo_url(external=True) } }
class OAuth2Client(ClientMixin, db.Datetimed, db.Document): secret = db.StringField(default=lambda: gen_salt(50)) name = db.StringField(required=True) description = db.StringField() owner = db.ReferenceField('User') organization = db.ReferenceField('Organization') image = db.ImageField(fs=images, basename=default_image_basename, thumbnails=[150, 25]) redirect_uris = db.ListField(db.StringField()) scopes = db.ListField(db.StringField(), default=['default']) confidential = db.BooleanField(default=False) internal = db.BooleanField(default=False) meta = {'collection': 'oauth2_client'} def __unicode__(self): return self.name @property def client_id(self): return str(self.id) @property def client_secret(self): return self.secret @property def default_redirect_uri(self): return self.redirect_uris[0] def get_default_redirect_uri(self): '''Implement required ClientMixin method''' return self.default_redirect_uri def check_redirect_uri(self, redirect_uri): '''Implement required ClientMixin method''' return redirect_uri in self.redirect_uris def check_client_secret(self, client_secret): return self.secret == client_secret def check_token_endpoint_auth_method(self, method): if not self.has_client_secret(): return method == 'none' return method in ('client_secret_post', 'client_secret_basic') def check_response_type(self, response_type): return True def check_grant_type(self, grant_type): return True def check_requested_scopes(self, scopes): allowed = set(self.scopes) return allowed.issuperset(set(scopes)) def has_client_secret(self): return bool(self.secret)
class User(db.Document, WithMetrics, UserMixin): slug = db.SlugField(max_length=255, required=True, populate_from='fullname') email = db.StringField(max_length=255, required=True) password = db.StringField() active = db.BooleanField() roles = db.ListField(db.ReferenceField(Role), default=[]) first_name = db.StringField(max_length=255, required=True) last_name = db.StringField(max_length=255, required=True) avatar_url = db.URLField() avatar = db.ImageField(fs=avatars, basename=default_image_basename, thumbnails=AVATAR_SIZES) website = db.URLField() about = db.StringField() prefered_language = db.StringField() apikey = db.StringField() created_at = db.DateTimeField(default=datetime.now, required=True) confirmed_at = db.DateTimeField() last_login_at = db.DateTimeField() current_login_at = db.DateTimeField() last_login_ip = db.StringField() current_login_ip = db.StringField() login_count = db.IntField() deleted = db.DateTimeField() ext = db.MapField(db.GenericEmbeddedDocumentField()) extras = db.ExtrasField() before_save = Signal() after_save = Signal() on_create = Signal() on_update = Signal() before_delete = Signal() after_delete = Signal() on_delete = Signal() meta = { 'allow_inheritance': True, 'indexes': ['-created_at', 'slug', 'apikey'], 'ordering': ['-created_at'] } def __str__(self): return self.fullname __unicode__ = __str__ @property def fullname(self): return ' '.join((self.first_name or '', self.last_name or '')).strip() @cached_property def organizations(self): from udata.core.organization.models import Organization return Organization.objects(members__user=self) @property def sysadmin(self): return self.has_role('admin') @property def display_url(self): return url_for('users.show', user=self) @property def visible(self): return (self.metrics.get('datasets', 0) + self.metrics.get('reuses', 0)) > 0 def generate_api_key(self): s = JSONWebSignatureSerializer(current_app.config['SECRET_KEY']) self.apikey = s.dumps({ 'user': str(self.id), 'time': time(), }) def clear_api_key(self): self.apikey = None @classmethod def get(cls, id_or_slug): obj = cls.objects(slug=id_or_slug).first() return obj or cls.objects.get_or_404(id=id_or_slug) @classmethod def pre_save(cls, sender, document, **kwargs): cls.before_save.send(document) @classmethod def post_save(cls, sender, document, **kwargs): cls.after_save.send(document) if kwargs.get('created'): cls.on_create.send(document) else: cls.on_update.send(document)
class GeoZone(db.Document): id = db.StringField(primary_key=True) name = db.StringField(required=True) level = db.StringField(required=True) code = db.StringField(unique_with='level') geom = db.MultiPolygonField(required=True) parents = db.ListField() keys = db.DictField() population = db.IntField() area = db.FloatField() wikipedia = db.StringField() dbpedia = db.StringField() logo = db.ImageField(fs=logos) meta = { 'indexes': [ 'name', 'parents', ('level', 'code'), ] } def __unicode__(self): return self.id __str__ = __unicode__ def __html__(self): return '{name} <i>({code})</i>'.format( name=gettext(self.name), code=self.code) def logo_url(self, external=False): filename = self.logo.filename if filename and self.logo.fs.exists(filename): return self.logo.fs.url(filename, external=external) else: return '' @property def keys_values(self): """Key values might be a list or not, always return a list.""" keys_values = [] for value in self.keys.values(): if isinstance(value, list): keys_values += value elif not str(value).startswith('-'): # Avoid -99. keys_values.append(value) return keys_values @property def url(self): return url_for('territories.territory', territory=self) @property def external_url(self): return url_for('territories.territory', territory=self, _external=True) @cached_property def wikipedia_url(self): return (self.dbpedia.replace('dbpedia', 'wikipedia') .replace('resource', 'wiki')) @cached_property def postal_string(self): """Return a list of postal codes separated by commas.""" return ', '.join(self.keys.get('postal', [])) @cached_property def town_repr(self): """Representation of a town with optional county.""" if self.county: return '{name} <small>({county_name})</small>'.format( name=self.name, county_name=self.county.name) return self.name @cached_property def county(self): for parent in self.parents: if parent.startswith('fr/county'): return GeoZone.objects.get(id=parent) def toGeoJSON(self): return { 'id': self.id, 'type': 'Feature', 'geometry': self.geom, 'properties': { 'name': _(self.name), 'level': self.level, 'code': self.code, 'parents': self.parents, 'keys': self.keys, 'population': self.population, 'area': self.area, } }
class Reuse(db.Datetimed, WithMetrics, BadgeMixin, db.Document): title = db.StringField(max_length=255, required=True) slug = db.SlugField(max_length=255, required=True, populate_from='title', update=True) description = db.StringField(required=True) type = db.StringField(required=True, choices=REUSE_TYPES.keys()) url = db.StringField(required=True) urlhash = db.StringField(required=True, unique=True) image_url = db.StringField() image = db.ImageField(fs=images, basename=default_image_basename, max_size=IMAGE_MAX_SIZE, thumbnails=IMAGE_SIZES) datasets = db.ListField( db.ReferenceField('Dataset', reverse_delete_rule=db.PULL)) tags = db.TagListField() # badges = db.ListField(db.EmbeddedDocumentField(ReuseBadge)) private = db.BooleanField() owner = db.ReferenceField('User', reverse_delete_rule=db.NULLIFY) organization = db.ReferenceField('Organization', reverse_delete_rule=db.NULLIFY) ext = db.MapField(db.GenericEmbeddedDocumentField()) extras = db.ExtrasField() featured = db.BooleanField() deleted = db.DateTimeField() def __str__(self): return self.title or '' __unicode__ = __str__ __badges__ = {} meta = { 'allow_inheritance': True, 'indexes': ['-created_at', 'owner', 'urlhash'], 'ordering': ['-created_at'], 'queryset_class': ReuseQuerySet, } before_save = Signal() after_save = Signal() on_create = Signal() on_update = Signal() before_delete = Signal() after_delete = Signal() on_delete = Signal() verbose_name = _('reuse') @classmethod def pre_save(cls, sender, document, **kwargs): # Emit before_save cls.before_save.send(document) @classmethod def post_save(cls, sender, document, **kwargs): cls.after_save.send(document) if kwargs.get('created'): cls.on_create.send(document) else: cls.on_update.send(document) def url_for(self, *args, **kwargs): return url_for('reuses.show', reuse=self, *args, **kwargs) display_url = property(url_for) @property def external_url(self): return self.url_for(_external=True) @property def type_label(self): return REUSE_TYPES[self.type] def clean(self): '''Auto populate urlhash from url''' if not self.urlhash or 'url' in self._get_changed_fields(): self.urlhash = hash_url(self.url) super(Reuse, self).clean() @classmethod def get(cls, id_or_slug): obj = cls.objects(slug=id_or_slug).first() return obj or cls.objects.get_or_404(id=id_or_slug) @classmethod def url_exists(cls, url): urlhash = hash_url(url) return cls.objects(urlhash=urlhash).count() > 0
class D(db.Document): image = db.ImageField(fs=storage) thumbnail = db.ImageField(fs=storage, thumbnails=SIZES)
class GeoZone(db.Document): id = db.StringField(primary_key=True) name = db.StringField(required=True) level = db.StringField(required=True) code = db.StringField(unique_with='level') geom = db.MultiPolygonField(required=True) parents = db.ListField() keys = db.DictField() population = db.IntField() area = db.FloatField() wikipedia = db.StringField() dbpedia = db.StringField() logo = db.ImageField(fs=logos) meta = { 'indexes': [ 'name', 'parents', ('level', 'code'), ] } def __unicode__(self): return self.id __str__ = __unicode__ def __html__(self): """In use within the admin.""" return '{name} <i>({code})</i>'.format(name=gettext(self.name), code=self.code) @cached_property def html_title(self): """In use within templates.""" if self.level_name == 'town': return ('{name} ' '<small>(<a href="{parent_url}">{parent_name}</a>)</small>' '').format(name=self.name, parent_url=self.parent.url, parent_name=self.parent.name) elif self.level_name == 'county': return '{name} <small>({code})</small>'.format(name=self.name, code=self.code) else: return self.name def logo_url(self, external=False): filename = self.logo.filename if filename and self.logo.fs.exists(filename): return self.logo.fs.url(filename, external=external) else: return '' @property def keys_values(self): """Key values might be a list or not, always return a list.""" keys_values = [] for value in self.keys.values(): if isinstance(value, list): keys_values += value elif not str(value).startswith('-'): # Avoid -99. keys_values.append(value) return keys_values @cached_property def level_name(self): """Truncated level name for the sake of readability.""" if self.level.startswith('fr/'): return self.level[3:] # Keep the whole level name as a fallback (e.g. `country/fr`) return self.level @cached_property def level_i18n_name(self): """In use within templates for dynamic translations.""" for level, name in spatial_granularities: if self.level == level: return name return self.level_name # Fallback that should never happen. @cached_property def child_level(self): """Return the child level given handled levels.""" HANDLED_LEVELS = current_app.config.get('HANDLED_LEVELS') try: return HANDLED_LEVELS[HANDLED_LEVELS.index(self.level) - 1] except (IndexError, ValueError): return None @cached_property def parent_level(self): """Return the parent level given handled levels.""" HANDLED_LEVELS = current_app.config.get('HANDLED_LEVELS') try: return HANDLED_LEVELS[HANDLED_LEVELS.index(self.level) + 1] except (IndexError, ValueError): return None @property def url(self): return url_for('territories.territory', territory=self) @property def external_url(self): return url_for('territories.territory', territory=self, _external=True) @cached_property def wikipedia_url(self): """Computed wikipedia URL from the DBpedia one.""" return (self.dbpedia.replace('dbpedia', 'wikipedia').replace('resource', 'wiki')) @cached_property def postal_string(self): """Return a list of postal codes separated by commas.""" return ', '.join(self.keys.get('postal', [])) @property def parent(self): if self.parent_level: for parent in self.parents: if parent.startswith(self.parent_level): return GeoZone.objects.get(id=parent, level=self.parent_level) @property def children(self): if self.child_level: return (GeoZone.objects(level=self.child_level, parents__in=[self.id]).order_by( '-population', '-area')) @property def handled_level(self): return self.level in current_app.config.get('HANDLED_LEVELS') def toGeoJSON(self): return { 'id': self.id, 'type': 'Feature', 'geometry': self.geom, 'properties': { 'name': gettext(self.name), 'level': self.level, 'code': self.code, 'parents': self.parents, 'keys': self.keys, 'population': self.population, 'area': self.area, 'logo': self.logo_url(external=True) } }
class User(WithMetrics, UserMixin, db.Document): slug = db.SlugField(max_length=255, required=True, populate_from='fullname') email = db.StringField(max_length=255, required=True, unique=True) password = db.StringField() active = db.BooleanField() roles = db.ListField(db.ReferenceField(Role), default=[]) first_name = db.StringField(max_length=255, required=True) last_name = db.StringField(max_length=255, required=True) avatar_url = db.URLField() avatar = db.ImageField(fs=avatars, basename=default_image_basename, thumbnails=AVATAR_SIZES) website = db.URLField() about = db.StringField() prefered_language = db.StringField() apikey = db.StringField() created_at = db.DateTimeField(default=datetime.now, required=True) # The field below is required for Flask-security # when SECURITY_CONFIRMABLE is True confirmed_at = db.DateTimeField() # The 5 fields below are required for Flask-security # when SECURITY_TRACKABLE is True last_login_at = db.DateTimeField() current_login_at = db.DateTimeField() last_login_ip = db.StringField() current_login_ip = db.StringField() login_count = db.IntField() deleted = db.DateTimeField() ext = db.MapField(db.GenericEmbeddedDocumentField()) extras = db.ExtrasField() before_save = Signal() after_save = Signal() on_create = Signal() on_update = Signal() before_delete = Signal() after_delete = Signal() on_delete = Signal() meta = { 'indexes': ['-created_at', 'slug', 'apikey'], 'ordering': ['-created_at'] } def __str__(self): return self.fullname __unicode__ = __str__ @property def fullname(self): return ' '.join((self.first_name or '', self.last_name or '')).strip() @cached_property def organizations(self): from udata.core.organization.models import Organization return Organization.objects(members__user=self, deleted__exists=False) @property def sysadmin(self): return self.has_role('admin') def url_for(self, *args, **kwargs): return url_for('users.show', user=self, *args, **kwargs) display_url = property(url_for) @property def external_url(self): return self.url_for(_external=True) @property def visible(self): count = self.metrics.get('datasets', 0) + self.metrics.get('reuses', 0) return count > 0 and self.active @cached_property def resources_availability(self): """Return the percentage of availability for resources.""" # Flatten the list. availabilities = list( chain(*[org.check_availability() for org in self.organizations])) if availabilities: # Trick will work because it's a sum() of booleans. return round(100. * sum(availabilities) / len(availabilities), 2) else: return 0 @cached_property def datasets_org_count(self): """Return the number of datasets of user's organizations.""" from udata.models import Dataset # Circular imports. return sum( Dataset.objects(organization=org).visible().count() for org in self.organizations) @cached_property def followers_org_count(self): """Return the number of followers of user's organizations.""" from udata.models import Follow # Circular imports. return sum( Follow.objects(following=org).count() for org in self.organizations) @property def datasets_count(self): """Return the number of datasets of the user.""" return self.metrics.get('datasets', 0) @property def followers_count(self): """Return the number of followers of the user.""" return self.metrics.get('followers', 0) def generate_api_key(self): s = JSONWebSignatureSerializer(current_app.config['SECRET_KEY']) self.apikey = s.dumps({ 'user': str(self.id), 'time': time(), }) def clear_api_key(self): self.apikey = None @classmethod def get(cls, id_or_slug): obj = cls.objects(slug=id_or_slug).first() return obj or cls.objects.get_or_404(id=id_or_slug) @classmethod def pre_save(cls, sender, document, **kwargs): cls.before_save.send(document) @classmethod def post_save(cls, sender, document, **kwargs): cls.after_save.send(document) if kwargs.get('created'): cls.on_create.send(document) else: cls.on_update.send(document) @cached_property def json_ld(self): result = { '@type': 'Person', '@context': 'http://schema.org', 'name': self.fullname, } if self.about: result['description'] = mdstrip(self.about) if self.avatar_url: result['image'] = self.avatar_url if self.website: result['url'] = self.website return result def mark_as_deleted(self): copied_user = copy(self) self.email = '{}@deleted'.format(self.id) self.password = None self.active = False self.first_name = 'DELETED' self.last_name = 'DELETED' self.avatar = None self.avatar_url = None self.website = None self.about = None self.deleted = datetime.now() self.save() for organization in self.organizations: organization.members = [ member for member in organization.members if member.user != self ] organization.save() for discussion in Discussion.objects(discussion__posted_by=self): for message in discussion.discussion: if message.posted_by == self: message.content = 'DELETED' discussion.save() Follow.objects(follower=self).delete() Follow.objects(following=self).delete() mail.send(_('Account deletion'), copied_user, 'account_deleted')
class Organization(WithMetrics, BadgeMixin, db.Datetimed, db.Document): name = db.StringField(required=True) acronym = db.StringField(max_length=128) slug = db.SlugField(max_length=255, required=True, populate_from='name', update=True, follow=True) description = db.StringField(required=True) url = db.StringField() image_url = db.StringField() logo = db.ImageField(fs=avatars, basename=default_image_basename, max_size=LOGO_MAX_SIZE, thumbnails=LOGO_SIZES) members = db.ListField(db.EmbeddedDocumentField(Member)) teams = db.ListField(db.EmbeddedDocumentField(Team)) requests = db.ListField(db.EmbeddedDocumentField(MembershipRequest)) ext = db.MapField(db.GenericEmbeddedDocumentField()) zone = db.StringField() extras = db.ExtrasField() deleted = db.DateTimeField() meta = { 'indexes': ['-created_at', 'slug'], 'ordering': ['-created_at'], 'queryset_class': OrganizationQuerySet, } def __str__(self): return self.name or '' __badges__ = { PUBLIC_SERVICE: _('Public Service'), CERTIFIED: _('Certified'), } __search_metrics__ = Object( properties={ 'datasets': Integer(), 'reuses': Integer(), 'followers': Integer(), 'views': Integer(), }) __metrics_keys__ = [ 'datasets', 'members', 'reuses', 'followers', 'views', ] before_save = Signal() after_save = Signal() on_create = Signal() on_update = Signal() before_delete = Signal() after_delete = Signal() @classmethod def pre_save(cls, sender, document, **kwargs): cls.before_save.send(document) @classmethod def post_save(cls, sender, document, **kwargs): cls.after_save.send(document) if kwargs.get('created'): cls.on_create.send(document) else: cls.on_update.send(document) def url_for(self, *args, **kwargs): return url_for('organizations.show', org=self, *args, **kwargs) display_url = property(url_for) @property def external_url(self): return self.url_for(_external=True) @property def pending_requests(self): return [r for r in self.requests if r.status == 'pending'] @property def refused_requests(self): return [r for r in self.requests if r.status == 'refused'] @property def accepted_requests(self): return [r for r in self.requests if r.status == 'accepted'] @property def certified(self): return any(b.kind == CERTIFIED for b in self.badges) @property def public_service(self): is_public_service = any(b.kind == PUBLIC_SERVICE for b in self.badges) return self.certified and is_public_service def member(self, user): for member in self.members: if member.user == user: return member return None def is_member(self, user): return self.member(user) is not None def is_admin(self, user): member = self.member(user) return member is not None and member.role == 'admin' def pending_request(self, user): for request in self.requests: if request.user == user and request.status == 'pending': return request return None @classmethod def get(cls, id_or_slug): obj = cls.objects(slug=id_or_slug).first() return obj or cls.objects.get_or_404(id=id_or_slug) def by_role(self, role): return filter(lambda m: m.role == role, self.members) def check_availability(self): from udata.models import Dataset # Circular imports. # Performances: only check the first 20 datasets for now. return chain(*[ dataset.check_availability() for dataset in Dataset.objects(organization=self).visible()[:20] ]) @cached_property def json_ld(self): type_ = 'GovernmentOrganization' if self.public_service \ else 'Organization' result = { '@context': 'http://schema.org', '@type': type_, '@id': str(self.id), 'alternateName': self.slug, 'url': url_for('organizations.show', org=self, _external=True), 'name': self.name, 'dateCreated': self.created_at.isoformat(), 'dateModified': self.last_modified.isoformat() } if self.description: result['description'] = mdstrip(self.description) logo = self.logo(external=True) if logo: result['logo'] = logo return result @property def views_count(self): return self.metrics.get('views', 0) def count_members(self): self.metrics['members'] = len(self.members) self.save() def count_datasets(self): from udata.models import Dataset self.metrics['datasets'] = Dataset.objects( organization=self).visible().count() self.save() def count_reuses(self): from udata.models import Reuse self.metrics['reuses'] = Reuse.objects(organization=self).count() self.save() def count_followers(self): from udata.models import Follow self.metrics['followers'] = Follow.objects( until=None).followers(self).count() self.save()
class Organization(WithMetrics, BadgeMixin, db.Datetimed, db.Document): name = db.StringField(max_length=255, required=True) acronym = db.StringField(max_length=128) slug = db.SlugField(max_length=255, required=True, populate_from='name', update=True) description = db.StringField(required=True) url = db.StringField() image_url = db.StringField() logo = db.ImageField(fs=avatars, basename=default_image_basename, thumbnails=LOGO_SIZES) members = db.ListField(db.EmbeddedDocumentField(Member)) teams = db.ListField(db.EmbeddedDocumentField(Team)) requests = db.ListField(db.EmbeddedDocumentField(MembershipRequest)) ext = db.MapField(db.GenericEmbeddedDocumentField()) zone = db.StringField() extras = db.ExtrasField() deleted = db.DateTimeField() meta = { 'allow_inheritance': True, 'indexes': ['-created_at', 'slug'], 'ordering': ['-created_at'], 'queryset_class': OrganizationQuerySet, } def __str__(self): return self.name or '' __unicode__ = __str__ __badges__ = { PUBLIC_SERVICE: _('Public Service'), CERTIFIED: _('Certified'), } before_save = Signal() after_save = Signal() on_create = Signal() on_update = Signal() before_delete = Signal() after_delete = Signal() @classmethod def pre_save(cls, sender, document, **kwargs): cls.before_save.send(document) @classmethod def post_save(cls, sender, document, **kwargs): cls.after_save.send(document) if kwargs.get('created'): cls.on_create.send(document) else: cls.on_update.send(document) def url_for(self, *args, **kwargs): return url_for('organizations.show', org=self, *args, **kwargs) display_url = property(url_for) @property def external_url(self): return self.url_for(_external=True) @property def pending_requests(self): return [r for r in self.requests if r.status == 'pending'] @property def refused_requests(self): return [r for r in self.requests if r.status == 'refused'] @property def accepted_requests(self): return [r for r in self.requests if r.status == 'accepted'] @property def public_service(self): badges_kind = [badge.kind for badge in self.badges] return PUBLIC_SERVICE in badges_kind and CERTIFIED in badges_kind def member(self, user): for member in self.members: if member.user == user: return member return None def is_member(self, user): return self.member(user) is not None def is_admin(self, user): member = self.member(user) return member is not None and member.role == 'admin' def pending_request(self, user): for request in self.requests: if request.user == user and request.status == 'pending': return request return None @classmethod def get(cls, id_or_slug): obj = cls.objects(slug=id_or_slug).first() return obj or cls.objects.get_or_404(id=id_or_slug) def by_role(self, role): return filter(lambda m: m.role == role, self.members) def check_availability(self): from udata.models import Dataset # Circular imports. # Performances: only check the first 20 datasets for now. return chain(*[ dataset.check_availability() for dataset in Dataset.objects(organization=self).visible()[:20] ])
class User(db.Document, WithMetrics, UserMixin): slug = db.SlugField(max_length=255, required=True, populate_from='fullname') email = db.StringField(max_length=255, required=True) password = db.StringField() active = db.BooleanField() roles = db.ListField(db.ReferenceField(Role), default=[]) first_name = db.StringField(max_length=255, required=True) last_name = db.StringField(max_length=255, required=True) avatar_url = db.URLField() avatar = db.ImageField(fs=avatars, basename=default_image_basename, thumbnails=AVATAR_SIZES) website = db.URLField() about = db.StringField() prefered_language = db.StringField() apikey = db.StringField() created_at = db.DateTimeField(default=datetime.now, required=True) confirmed_at = db.DateTimeField() last_login_at = db.DateTimeField() current_login_at = db.DateTimeField() last_login_ip = db.StringField() current_login_ip = db.StringField() login_count = db.IntField() deleted = db.DateTimeField() ext = db.MapField(db.GenericEmbeddedDocumentField()) extras = db.ExtrasField() before_save = Signal() after_save = Signal() on_create = Signal() on_update = Signal() before_delete = Signal() after_delete = Signal() on_delete = Signal() meta = { 'allow_inheritance': True, 'indexes': ['-created_at', 'slug', 'apikey'], 'ordering': ['-created_at'] } def __str__(self): return self.fullname __unicode__ = __str__ @property def fullname(self): return ' '.join((self.first_name or '', self.last_name or '')).strip() @cached_property def organizations(self): from udata.core.organization.models import Organization return Organization.objects(members__user=self) @property def sysadmin(self): return self.has_role('admin') def url_for(self, *args, **kwargs): return url_for('users.show', user=self, *args, **kwargs) display_url = property(url_for) @property def external_url(self): return self.url_for(_external=True) @property def visible(self): count = self.metrics.get('datasets', 0) + self.metrics.get('reuses', 0) return count > 0 @cached_property def resources_availability(self): """Return the percentage of availability for resources.""" # Flatten the list. availabilities = list( chain(*[org.check_availability() for org in self.organizations])) if availabilities: # Trick will work because it's a sum() of booleans. return round(100. * sum(availabilities) / len(availabilities), 2) else: return 0 @cached_property def datasets_org_count(self): """Return the number of datasets of user's organizations.""" from udata.models import Dataset # Circular imports. return sum( Dataset.objects(organization=org).visible().count() for org in self.organizations) @cached_property def followers_org_count(self): """Return the number of followers of user's organizations.""" from udata.models import FollowOrg # Circular imports. return sum( FollowOrg.objects(following=org).count() for org in self.organizations) @property def datasets_count(self): """Return the number of datasets of the user.""" return self.metrics.get('datasets', 0) @property def followers_count(self): """Return the number of followers of the user.""" return self.metrics.get('followers', 0) def generate_api_key(self): s = JSONWebSignatureSerializer(current_app.config['SECRET_KEY']) self.apikey = s.dumps({ 'user': str(self.id), 'time': time(), }) def clear_api_key(self): self.apikey = None @classmethod def get(cls, id_or_slug): obj = cls.objects(slug=id_or_slug).first() return obj or cls.objects.get_or_404(id=id_or_slug) @classmethod def pre_save(cls, sender, document, **kwargs): cls.before_save.send(document) @classmethod def post_save(cls, sender, document, **kwargs): cls.after_save.send(document) if kwargs.get('created'): cls.on_create.send(document) else: cls.on_update.send(document)
class Organization(WithMetrics, db.Datetimed, db.Document): name = db.StringField(max_length=255, required=True) acronym = db.StringField(max_length=128) slug = db.SlugField(max_length=255, required=True, populate_from='name', update=True) description = db.StringField(required=True) url = db.StringField() image_url = db.StringField() logo = db.ImageField(fs=avatars, basename=default_image_basename, thumbnails=LOGO_SIZES) members = db.ListField(db.EmbeddedDocumentField(Member)) teams = db.ListField(db.EmbeddedDocumentField(Team)) requests = db.ListField(db.EmbeddedDocumentField(MembershipRequest)) ext = db.MapField(db.GenericEmbeddedDocumentField()) extras = db.ExtrasField() deleted = db.DateTimeField() # TODO: Extract into extension public_service = db.BooleanField() meta = { 'allow_inheritance': True, 'indexes': ['-created_at', 'slug'], 'ordering': ['-created_at'], 'queryset_class': OrganizationQuerySet, } def __str__(self): return self.name or '' __unicode__ = __str__ before_save = Signal() after_save = Signal() on_create = Signal() on_update = Signal() before_delete = Signal() after_delete = Signal() @classmethod def pre_save(cls, sender, document, **kwargs): cls.before_save.send(document) @classmethod def post_save(cls, sender, document, **kwargs): cls.after_save.send(document) if kwargs.get('created'): cls.on_create.send(document) else: cls.on_update.send(document) @property def display_url(self): return url_for('organizations.show', org=self) @property def external_url(self): return url_for('organizations.show', org=self, _external=True) @property def pending_requests(self): return [r for r in self.requests if r.status == 'pending'] @property def refused_requests(self): return [r for r in self.requests if r.status == 'refused'] @property def accepted_requests(self): return [r for r in self.requests if r.status == 'accepted'] def member(self, user): for member in self.members: if member.user == user: return member return None def is_member(self, user): return self.member(user) is not None def is_admin(self, user): member = self.member(user) return member is not None and member.role == 'admin' def pending_request(self, user): for request in self.requests: if request.user == user and request.status == 'pending': return request return None @classmethod def get(cls, id_or_slug): obj = cls.objects(slug=id_or_slug).first() return obj or cls.objects.get_or_404(id=id_or_slug) def by_role(self, role): return filter(lambda m: m.role == role, self.members)