class Spike(BlockBasedPermissionsMixin, BaseInfo, DataObject): """ NEO Spike @ G-Node. """ # NEO attributes name = models.CharField(max_length=DEFAULTS['name_max_length'], blank=True, null=True) time = models.FloatField() time__unit = TimeUnitField('time__unit', default=DEFAULTS['default_time_unit']) sampling_rate = models.FloatField('sampling_rate', blank=True, null=True) sampling_rate__unit = SamplingUnitField('sampling_rate__unit', blank=True, null=True) left_sweep = models.FloatField('left_sweep', blank=True, null=True) left_sweep__unit = TimeUnitField('left_sweep__unit', blank=True, null=True) # NEO data arrays waveform = models.FileField(storage=fs, upload_to=make_upload_path, blank=True, null=True) waveform__unit = SignalUnitField('waveform__unit', default=DEFAULTS['default_data_unit']) # NEO relationships segment = VersionedForeignKey(Segment) unit = VersionedForeignKey(Unit, blank=True, null=True) def save(self, *args, **kwargs): self.block = self.segment.block super(Spike, self).save(*args, **kwargs)
class AnalogSignal(BlockBasedPermissionsMixin, BaseInfo, DataObject): """ NEO AnalogSignal @ G-Node. """ # NEO attributes name = models.CharField(max_length=DEFAULTS['name_max_length'], blank=True, null=True) sampling_rate = models.FloatField('sampling_rate') sampling_rate__unit = SamplingUnitField( 'sampling_rate__unit', default=DEFAULTS['default_samp_unit']) t_start = models.FloatField('t_start') t_start__unit = TimeUnitField('t_start__unit', default=DEFAULTS['default_time_unit']) # NEO relationships segment = VersionedForeignKey(Segment) recordingchannel = VersionedForeignKey(RecordingChannel, blank=True, null=True) # NEO data arrays signal = models.FileField(storage=fs, upload_to=make_upload_path, blank=True, null=True) signal__unit = SignalUnitField('signal__unit', default=DEFAULTS['default_data_unit']) def save(self, *args, **kwargs): self.block = self.segment.block super(AnalogSignal, self).save(*args, **kwargs)
class EpochArray(BlockBasedPermissionsMixin, BaseInfo, DataObject): """ NEO EpochArray @ G-Node. """ name = models.CharField(max_length=DEFAULTS['name_max_length'], blank=True, null=True) # NEO data arrays labels = models.FileField(storage=fs, upload_to=make_upload_path, blank=True, null=True) times = models.FileField(storage=fs, upload_to=make_upload_path, blank=True, null=True) times__unit = TimeUnitField('times__unit', default=DEFAULTS['default_time_unit']) durations = models.FileField(storage=fs, upload_to=make_upload_path, blank=True, null=True) durations__unit = TimeUnitField('durations__unit', default=DEFAULTS['default_time_unit']) # NEO relationships segment = VersionedForeignKey(Segment) def save(self, *args, **kwargs): self.block = self.segment.block super(EpochArray, self).save(*args, **kwargs)
class ACLFakeModel(BaseGnodeObject): """ simple versioned model """ test_attr = models.IntegerField() test_str_attr = models.CharField(max_length=50, blank=True) test_ref = VersionedForeignKey( ACLFakeOwnedModel, blank=True, null=True, on_delete=models.SET_NULL )
class BaseInfo(BaseGnodeObject): """ Base abstract class for any NEO object (except Block) """ # block reference is used to determine access to an object block = VersionedForeignKey(Block) metadata = VersionedForeignKey(Section, null=True, blank=True) description = models.CharField(max_length=1024, blank=True, null=True) class Meta: abstract = True @property def has_data(self): return False # no data by default @property def size(self): raise NotImplementedError()
class Value(DocumentBasedPermissionsMixin, BaseGnodeObject): """ Class implemented metadata Value. """ data = models.TextField('data') uncertainty = models.CharField(max_length=100, blank=True, null=True) unit = models.CharField(max_length=100, blank=True, null=True) reference = models.CharField(max_length=100, blank=True, null=True) definition = models.CharField(max_length=100, blank=True, null=True) filename = models.CharField(max_length=100, blank=True, null=True) encoder = models.CharField(max_length=100, blank=True, null=True) checksum = models.CharField(max_length=100, blank=True, null=True) property = VersionedForeignKey(Property, on_delete=models.CASCADE) document = VersionedForeignKey(Document) def save(self, *args, **kwargs): self.document = self.property.document super(Value, self).save(*args, **kwargs)
class Property(DocumentBasedPermissionsMixin, BaseGnodeObject): """ Class represents a metadata "Property". Defines any kind of metadata property and may be linked to the Section. """ # odML fields name = models.CharField('name', max_length=100) definition = models.CharField(max_length=100, blank=True, null=True) mapping = models.CharField(max_length=100, blank=True, null=True) dependency = models.CharField(max_length=100, blank=True, null=True) dependencyvalue = models.CharField(max_length=100, blank=True, null=True) section = VersionedForeignKey(Section, on_delete=models.CASCADE) document = VersionedForeignKey(Document) def save(self, *args, **kwargs): self.document = self.section.document super(Property, self).save(*args, **kwargs)
class IrregularlySampledSignal(BlockBasedPermissionsMixin, BaseInfo, DataObject): """ NEO IrregularlySampledSignal @ G-Node. """ # NEO attributes name = models.CharField(max_length=DEFAULTS['name_max_length'], blank=True, null=True) t_start = models.FloatField('t_start') t_start__unit = TimeUnitField('t_start__unit', default=DEFAULTS['default_time_unit']) # NEO relationships segment = VersionedForeignKey(Segment) recordingchannel = VersionedForeignKey(RecordingChannel, blank=True, null=True) # NEO data arrays signal = models.FileField(storage=fs, upload_to=make_upload_path, blank=True, null=True) signal__unit = SignalUnitField('signal__unit', default=DEFAULTS['default_data_unit']) times = models.FileField(storage=fs, upload_to=make_upload_path, blank=True, null=True) times__unit = TimeUnitField('times__unit', default=DEFAULTS['default_time_unit']) def full_clean(self, *args, **kwargs): """ Add some validation to keep 'signal' and 'times' dimensions consistent. Currently switched off. """ super(IrregularlySampledSignal, self).full_clean(*args, **kwargs) def save(self, *args, **kwargs): self.block = self.segment.block super(IrregularlySampledSignal, self).save(*args, **kwargs)
class Unit(BlockBasedPermissionsMixin, BaseInfo): """ NEO Unit @ G-Node. """ name = models.CharField(max_length=DEFAULTS['name_max_length']) # NEO relationships recordingchannelgroup = VersionedForeignKey(RecordingChannelGroup) def save(self, *args, **kwargs): self.block = self.recordingchannelgroup.block super(Unit, self).save(*args, **kwargs)
class SpikeTrain(BlockBasedPermissionsMixin, BaseInfo, DataObject): """ NEO SpikeTrain @ G-Node. """ # NEO attributes name = models.CharField(max_length=DEFAULTS['name_max_length'], blank=True, null=True) t_start = models.FloatField('t_start') t_start__unit = TimeUnitField('t_start__unit', default=DEFAULTS['default_time_unit']) t_stop = models.FloatField('t_stop') t_stop__unit = TimeUnitField('t_stop__unit', default=DEFAULTS['default_time_unit']) # NEO relationships segment = VersionedForeignKey(Segment) unit = VersionedForeignKey(Unit, blank=True, null=True) # NEO data arrays times = models.FileField(storage=fs, upload_to=make_upload_path, blank=True, null=True) times__unit = TimeUnitField('times__unit', default=DEFAULTS['default_time_unit']) waveforms = models.FileField(storage=fs, upload_to=make_upload_path, blank=True, null=True) waveforms__unit = SignalUnitField('waveforms__unit', blank=True, null=True) # rewrite the size property when waveforms are supported def compute_size(self): return self.times.size def save(self, *args, **kwargs): self.block = self.segment.block super(SpikeTrain, self).save(*args, **kwargs)
class Block(BasePermissionsMixin, BaseGnodeObject): """ NEO Block @ G-Node. """ # NEO attributes name = models.CharField(max_length=DEFAULTS['name_max_length']) description = models.CharField(max_length=1024, blank=True, null=True) filedatetime = models.DateTimeField(null=True, blank=True) index = models.IntegerField(null=True, blank=True) metadata = VersionedForeignKey(Section, null=True, blank=True) @property def size(self): return sum([s.size for s in self.segment_set.all()])
class Event(BlockBasedPermissionsMixin, BaseInfo): """ NEO Event @ G-Node. """ # NEO attributes name = models.CharField(max_length=DEFAULTS['name_max_length'], blank=True, null=True) label = models.CharField('label', max_length=DEFAULTS['name_max_length']) time = models.FloatField('time') time__unit = TimeUnitField('time__unit', default=DEFAULTS['default_time_unit']) # NEO relationships segment = VersionedForeignKey(Segment) def save(self, *args, **kwargs): self.block = self.segment.block super(Event, self).save(*args, **kwargs)
class parent_fake(VersionedM2M): """ M2M relationship class """ parent = VersionedForeignKey(FakeParentModel) fake = VersionedForeignKey(FakeModel)
class FakeChildModel(BaseGnodeObject): """ simple versioned model with parent """ test_attr = models.IntegerField() test_ref = VersionedForeignKey( FakeParentModel, blank=True, null=True, on_delete=models.SET_NULL )
class recordingchannel_rcg(VersionedM2M): recordingchannelgroup = VersionedForeignKey(RecordingChannelGroup) recordingchannel = VersionedForeignKey(RecordingChannel)
class Section(DocumentBasedPermissionsMixin, BaseGnodeObject): """ Class represents a metadata "Section". Used to organize metadata (properties - values), Datafiles and NEO Blocks in a tree-like structure. May be recursively linked to itself. May be made public or shared with specific users via sharing of the related Document. Document field is always set, even for low-level Sections in the odML hierarchy. This is done to avoid recursive SQL-calls to fetch appropriate Section permissions. """ # odML fields name = models.CharField(max_length=100) type = models.CharField(max_length=50, blank=True, null=True) reference = models.CharField(max_length=100, blank=True, null=True) definition = models.CharField(max_length=100, blank=True, null=True) link = models.CharField(max_length=100, blank=True, null=True) include = models.CharField(max_length=100, blank=True, null=True) repository = models.CharField(max_length=100, blank=True, null=True) mapping = models.CharField(max_length=100, blank=True, null=True) section = VersionedForeignKey( 'self', blank=True, null=True, related_name='section_set', on_delete=models.CASCADE ) document = VersionedForeignKey( Document, blank=True, null=True, on_delete=models.CASCADE ) # position in the list on the same level in the tree tree_position = models.IntegerField(default=0) # field indicates whether it is a "template" section is_template = models.BooleanField(default=False) # manager that proxies correct QuerySet objects = SectionManager() @property def sections(self): return self.section_set.order_by("-tree_position") def fetch_deep_ids(self, ids): """ return all section ids located inside sections with given ids. This function as many sql calls as there are levels of the section tree """ qs = self.__class__.objects.filter(parent_section_id__in=ids) down_one_level = list(qs.values_list('pk', flat=True)) if down_one_level: return ids + self.fetch_deep_ids(down_one_level) return ids def stats(self, cascade=False): """ Section statistics """ sec_ids = [self.pk] if cascade: # recursively traverse a tree of child sections sec_ids = self.fetch_deep_ids(sec_ids) stats = {} # calculate section statistics for rm in self._meta.get_all_related_objects(): if not rm.model == self.__class__: kwargs = {rm.field.name + '_id__in': sec_ids} v = rm.model.objects.filter(**kwargs).count() else: v = len(sec_ids) - 1 # exclude self stats[rm.name] = v return stats def _get_next_tree_pos(self): """ Returns the next tree index "inside" self. """ if self.sections: return int(self.sections[0].tree_position) + 1 return 1 def save(self, *args, **kwargs): def obj_has_changed(old, new): return (old is None and new is not None) or \ (new is None and old is not None) or \ (old is not None and new is not None and not old.pk == new.pk) if self.pk is not None and self.pk != "": # update case old = self.__class__.objects.get(pk=self.pk) if obj_has_changed(old.section, self.section): if not self.section is None: self.document = self.section.document elif obj_has_changed(old.document, self.document): if not old.section is None: raise ValueError("Clean parent section to change Document") else: # create case # section has a priority. if section is set, the document of the new # section will be taken from the parent section, not from the # current 'document' attribute if self.section is None and self.document is None: raise ValidationError("Either document or section must present") if self.section is not None: self.document = self.section.document self.full_clean() super(Section, self).save(*args, **kwargs)